162306a36Sopenharmony_ci/*
262306a36Sopenharmony_ci * drivers/mtd/maps/intel_vr_nor.c
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * An MTD map driver for a NOR flash bank on the Expansion Bus of the Intel
562306a36Sopenharmony_ci * Vermilion Range chipset.
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * The Vermilion Range Expansion Bus supports four chip selects, each of which
862306a36Sopenharmony_ci * has 64MiB of address space.  The 2nd BAR of the Expansion Bus PCI Device
962306a36Sopenharmony_ci * is a 256MiB memory region containing the address spaces for all four of the
1062306a36Sopenharmony_ci * chip selects, with start addresses hardcoded on 64MiB boundaries.
1162306a36Sopenharmony_ci *
1262306a36Sopenharmony_ci * This map driver only supports NOR flash on chip select 0.  The buswidth
1362306a36Sopenharmony_ci * (either 8 bits or 16 bits) is determined by reading the Expansion Bus Timing
1462306a36Sopenharmony_ci * and Control Register for Chip Select 0 (EXP_TIMING_CS0).  This driver does
1562306a36Sopenharmony_ci * not modify the value in the EXP_TIMING_CS0 register except to enable writing
1662306a36Sopenharmony_ci * and disable boot acceleration.  The timing parameters in the register are
1762306a36Sopenharmony_ci * assumed to have been properly initialized by the BIOS.  The reset default
1862306a36Sopenharmony_ci * timing parameters are maximally conservative (slow), so access to the flash
1962306a36Sopenharmony_ci * will be slower than it should be if the BIOS has not initialized the timing
2062306a36Sopenharmony_ci * parameters.
2162306a36Sopenharmony_ci *
2262306a36Sopenharmony_ci * Author: Andy Lowe <alowe@mvista.com>
2362306a36Sopenharmony_ci *
2462306a36Sopenharmony_ci * 2006 (c) MontaVista Software, Inc. This file is licensed under
2562306a36Sopenharmony_ci * the terms of the GNU General Public License version 2. This program
2662306a36Sopenharmony_ci * is licensed "as is" without any warranty of any kind, whether express
2762306a36Sopenharmony_ci * or implied.
2862306a36Sopenharmony_ci */
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci#include <linux/module.h>
3162306a36Sopenharmony_ci#include <linux/kernel.h>
3262306a36Sopenharmony_ci#include <linux/slab.h>
3362306a36Sopenharmony_ci#include <linux/pci.h>
3462306a36Sopenharmony_ci#include <linux/mtd/mtd.h>
3562306a36Sopenharmony_ci#include <linux/mtd/map.h>
3662306a36Sopenharmony_ci#include <linux/mtd/partitions.h>
3762306a36Sopenharmony_ci#include <linux/mtd/cfi.h>
3862306a36Sopenharmony_ci#include <linux/mtd/flashchip.h>
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci#define DRV_NAME "vr_nor"
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistruct vr_nor_mtd {
4362306a36Sopenharmony_ci	void __iomem *csr_base;
4462306a36Sopenharmony_ci	struct map_info map;
4562306a36Sopenharmony_ci	struct mtd_info *info;
4662306a36Sopenharmony_ci	struct pci_dev *dev;
4762306a36Sopenharmony_ci};
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci/* Expansion Bus Configuration and Status Registers are in BAR 0 */
5062306a36Sopenharmony_ci#define EXP_CSR_MBAR 0
5162306a36Sopenharmony_ci/* Expansion Bus Memory Window is BAR 1 */
5262306a36Sopenharmony_ci#define EXP_WIN_MBAR 1
5362306a36Sopenharmony_ci/* Maximum address space for Chip Select 0 is 64MiB */
5462306a36Sopenharmony_ci#define CS0_SIZE 0x04000000
5562306a36Sopenharmony_ci/* Chip Select 0 is at offset 0 in the Memory Window */
5662306a36Sopenharmony_ci#define CS0_START 0x0
5762306a36Sopenharmony_ci/* Chip Select 0 Timing Register is at offset 0 in CSR */
5862306a36Sopenharmony_ci#define EXP_TIMING_CS0 0x00
5962306a36Sopenharmony_ci#define TIMING_CS_EN		(1 << 31)	/* Chip Select Enable */
6062306a36Sopenharmony_ci#define TIMING_BOOT_ACCEL_DIS	(1 <<  8)	/* Boot Acceleration Disable */
6162306a36Sopenharmony_ci#define TIMING_WR_EN		(1 <<  1)	/* Write Enable */
6262306a36Sopenharmony_ci#define TIMING_BYTE_EN		(1 <<  0)	/* 8-bit vs 16-bit bus */
6362306a36Sopenharmony_ci#define TIMING_MASK		0x3FFF0000
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_cistatic void vr_nor_destroy_partitions(struct vr_nor_mtd *p)
6662306a36Sopenharmony_ci{
6762306a36Sopenharmony_ci	mtd_device_unregister(p->info);
6862306a36Sopenharmony_ci}
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_cistatic int vr_nor_init_partitions(struct vr_nor_mtd *p)
7162306a36Sopenharmony_ci{
7262306a36Sopenharmony_ci	/* register the flash bank */
7362306a36Sopenharmony_ci	/* partition the flash bank */
7462306a36Sopenharmony_ci	return mtd_device_register(p->info, NULL, 0);
7562306a36Sopenharmony_ci}
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_cistatic void vr_nor_destroy_mtd_setup(struct vr_nor_mtd *p)
7862306a36Sopenharmony_ci{
7962306a36Sopenharmony_ci	map_destroy(p->info);
8062306a36Sopenharmony_ci}
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_cistatic int vr_nor_mtd_setup(struct vr_nor_mtd *p)
8362306a36Sopenharmony_ci{
8462306a36Sopenharmony_ci	static const char * const probe_types[] =
8562306a36Sopenharmony_ci	    { "cfi_probe", "jedec_probe", NULL };
8662306a36Sopenharmony_ci	const char * const *type;
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	for (type = probe_types; !p->info && *type; type++)
8962306a36Sopenharmony_ci		p->info = do_map_probe(*type, &p->map);
9062306a36Sopenharmony_ci	if (!p->info)
9162306a36Sopenharmony_ci		return -ENODEV;
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	p->info->dev.parent = &p->dev->dev;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	return 0;
9662306a36Sopenharmony_ci}
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_cistatic void vr_nor_destroy_maps(struct vr_nor_mtd *p)
9962306a36Sopenharmony_ci{
10062306a36Sopenharmony_ci	unsigned int exp_timing_cs0;
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	/* write-protect the flash bank */
10362306a36Sopenharmony_ci	exp_timing_cs0 = readl(p->csr_base + EXP_TIMING_CS0);
10462306a36Sopenharmony_ci	exp_timing_cs0 &= ~TIMING_WR_EN;
10562306a36Sopenharmony_ci	writel(exp_timing_cs0, p->csr_base + EXP_TIMING_CS0);
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	/* unmap the flash window */
10862306a36Sopenharmony_ci	iounmap(p->map.virt);
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	/* unmap the csr window */
11162306a36Sopenharmony_ci	iounmap(p->csr_base);
11262306a36Sopenharmony_ci}
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci/*
11562306a36Sopenharmony_ci * Initialize the map_info structure and map the flash.
11662306a36Sopenharmony_ci * Returns 0 on success, nonzero otherwise.
11762306a36Sopenharmony_ci */
11862306a36Sopenharmony_cistatic int vr_nor_init_maps(struct vr_nor_mtd *p)
11962306a36Sopenharmony_ci{
12062306a36Sopenharmony_ci	unsigned long csr_phys, csr_len;
12162306a36Sopenharmony_ci	unsigned long win_phys, win_len;
12262306a36Sopenharmony_ci	unsigned int exp_timing_cs0;
12362306a36Sopenharmony_ci	int err;
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	csr_phys = pci_resource_start(p->dev, EXP_CSR_MBAR);
12662306a36Sopenharmony_ci	csr_len = pci_resource_len(p->dev, EXP_CSR_MBAR);
12762306a36Sopenharmony_ci	win_phys = pci_resource_start(p->dev, EXP_WIN_MBAR);
12862306a36Sopenharmony_ci	win_len = pci_resource_len(p->dev, EXP_WIN_MBAR);
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	if (!csr_phys || !csr_len || !win_phys || !win_len)
13162306a36Sopenharmony_ci		return -ENODEV;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	if (win_len < (CS0_START + CS0_SIZE))
13462306a36Sopenharmony_ci		return -ENXIO;
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	p->csr_base = ioremap(csr_phys, csr_len);
13762306a36Sopenharmony_ci	if (!p->csr_base)
13862306a36Sopenharmony_ci		return -ENOMEM;
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	exp_timing_cs0 = readl(p->csr_base + EXP_TIMING_CS0);
14162306a36Sopenharmony_ci	if (!(exp_timing_cs0 & TIMING_CS_EN)) {
14262306a36Sopenharmony_ci		dev_warn(&p->dev->dev, "Expansion Bus Chip Select 0 "
14362306a36Sopenharmony_ci		       "is disabled.\n");
14462306a36Sopenharmony_ci		err = -ENODEV;
14562306a36Sopenharmony_ci		goto release;
14662306a36Sopenharmony_ci	}
14762306a36Sopenharmony_ci	if ((exp_timing_cs0 & TIMING_MASK) == TIMING_MASK) {
14862306a36Sopenharmony_ci		dev_warn(&p->dev->dev, "Expansion Bus Chip Select 0 "
14962306a36Sopenharmony_ci		       "is configured for maximally slow access times.\n");
15062306a36Sopenharmony_ci	}
15162306a36Sopenharmony_ci	p->map.name = DRV_NAME;
15262306a36Sopenharmony_ci	p->map.bankwidth = (exp_timing_cs0 & TIMING_BYTE_EN) ? 1 : 2;
15362306a36Sopenharmony_ci	p->map.phys = win_phys + CS0_START;
15462306a36Sopenharmony_ci	p->map.size = CS0_SIZE;
15562306a36Sopenharmony_ci	p->map.virt = ioremap(p->map.phys, p->map.size);
15662306a36Sopenharmony_ci	if (!p->map.virt) {
15762306a36Sopenharmony_ci		err = -ENOMEM;
15862306a36Sopenharmony_ci		goto release;
15962306a36Sopenharmony_ci	}
16062306a36Sopenharmony_ci	simple_map_init(&p->map);
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	/* Enable writes to flash bank */
16362306a36Sopenharmony_ci	exp_timing_cs0 |= TIMING_BOOT_ACCEL_DIS | TIMING_WR_EN;
16462306a36Sopenharmony_ci	writel(exp_timing_cs0, p->csr_base + EXP_TIMING_CS0);
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	return 0;
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci      release:
16962306a36Sopenharmony_ci	iounmap(p->csr_base);
17062306a36Sopenharmony_ci	return err;
17162306a36Sopenharmony_ci}
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_cistatic const struct pci_device_id vr_nor_pci_ids[] = {
17462306a36Sopenharmony_ci	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x500D)},
17562306a36Sopenharmony_ci	{0,}
17662306a36Sopenharmony_ci};
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_cistatic void vr_nor_pci_remove(struct pci_dev *dev)
17962306a36Sopenharmony_ci{
18062306a36Sopenharmony_ci	struct vr_nor_mtd *p = pci_get_drvdata(dev);
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	vr_nor_destroy_partitions(p);
18362306a36Sopenharmony_ci	vr_nor_destroy_mtd_setup(p);
18462306a36Sopenharmony_ci	vr_nor_destroy_maps(p);
18562306a36Sopenharmony_ci	kfree(p);
18662306a36Sopenharmony_ci	pci_release_regions(dev);
18762306a36Sopenharmony_ci	pci_disable_device(dev);
18862306a36Sopenharmony_ci}
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_cistatic int vr_nor_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
19162306a36Sopenharmony_ci{
19262306a36Sopenharmony_ci	struct vr_nor_mtd *p = NULL;
19362306a36Sopenharmony_ci	unsigned int exp_timing_cs0;
19462306a36Sopenharmony_ci	int err;
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	err = pci_enable_device(dev);
19762306a36Sopenharmony_ci	if (err)
19862306a36Sopenharmony_ci		goto out;
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	err = pci_request_regions(dev, DRV_NAME);
20162306a36Sopenharmony_ci	if (err)
20262306a36Sopenharmony_ci		goto disable_dev;
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	p = kzalloc(sizeof(*p), GFP_KERNEL);
20562306a36Sopenharmony_ci	err = -ENOMEM;
20662306a36Sopenharmony_ci	if (!p)
20762306a36Sopenharmony_ci		goto release;
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	p->dev = dev;
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	err = vr_nor_init_maps(p);
21262306a36Sopenharmony_ci	if (err)
21362306a36Sopenharmony_ci		goto release;
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	err = vr_nor_mtd_setup(p);
21662306a36Sopenharmony_ci	if (err)
21762306a36Sopenharmony_ci		goto destroy_maps;
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci	err = vr_nor_init_partitions(p);
22062306a36Sopenharmony_ci	if (err)
22162306a36Sopenharmony_ci		goto destroy_mtd_setup;
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci	pci_set_drvdata(dev, p);
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	return 0;
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci      destroy_mtd_setup:
22862306a36Sopenharmony_ci	map_destroy(p->info);
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci      destroy_maps:
23162306a36Sopenharmony_ci	/* write-protect the flash bank */
23262306a36Sopenharmony_ci	exp_timing_cs0 = readl(p->csr_base + EXP_TIMING_CS0);
23362306a36Sopenharmony_ci	exp_timing_cs0 &= ~TIMING_WR_EN;
23462306a36Sopenharmony_ci	writel(exp_timing_cs0, p->csr_base + EXP_TIMING_CS0);
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	/* unmap the flash window */
23762306a36Sopenharmony_ci	iounmap(p->map.virt);
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci	/* unmap the csr window */
24062306a36Sopenharmony_ci	iounmap(p->csr_base);
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci      release:
24362306a36Sopenharmony_ci	kfree(p);
24462306a36Sopenharmony_ci	pci_release_regions(dev);
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci      disable_dev:
24762306a36Sopenharmony_ci	pci_disable_device(dev);
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci      out:
25062306a36Sopenharmony_ci	return err;
25162306a36Sopenharmony_ci}
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_cistatic struct pci_driver vr_nor_pci_driver = {
25462306a36Sopenharmony_ci	.name = DRV_NAME,
25562306a36Sopenharmony_ci	.probe = vr_nor_pci_probe,
25662306a36Sopenharmony_ci	.remove = vr_nor_pci_remove,
25762306a36Sopenharmony_ci	.id_table = vr_nor_pci_ids,
25862306a36Sopenharmony_ci};
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_cimodule_pci_driver(vr_nor_pci_driver);
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ciMODULE_AUTHOR("Andy Lowe");
26362306a36Sopenharmony_ciMODULE_DESCRIPTION("MTD map driver for NOR flash on Intel Vermilion Range");
26462306a36Sopenharmony_ciMODULE_LICENSE("GPL");
26562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(pci, vr_nor_pci_ids);
266