summaryrefslogtreecommitdiffstats
path: root/drivers/mtd/maps/scb2_flash.c
blob: 7e329f09a548f06bb83c36c1281d65db2278ef7c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
/*
 * MTD map driver for BIOS Flash on Intel SCB2 boards
 * Copyright (C) 2002 Sun Microsystems, Inc.
 * Tim Hockin <thockin@sun.com>
 *
 * A few notes on this MTD map:
 *
 * This was developed with a small number of SCB2 boards to test on.
 * Hopefully, Intel has not introducted too many unaccounted variables in the
 * making of this board.
 *
 * The BIOS marks its own memory region as 'reserved' in the e820 map.  We
 * try to request it here, but if it fails, we carry on anyway.
 *
 * This is how the chip is attached, so said the schematic:
 * * a 4 MiB (32 Mib) 16 bit chip
 * * a 1 MiB memory region
 * * A20 and A21 pulled up
 * * D8-D15 ignored
 * What this means is that, while we are addressing bytes linearly, we are
 * really addressing words, and discarding the other byte.  This means that
 * the chip MUST BE at least 2 MiB.  This also means that every block is
 * actually half as big as the chip reports.  It also means that accesses of
 * logical address 0 hit higher-address sections of the chip, not physical 0.
 * One can only hope that these 4MiB x16 chips were a lot cheaper than 1MiB x8
 * chips.
 *
 * This driver assumes the chip is not write-protected by an external signal.
 * As of the this writing, that is true, but may change, just to spite me.
 *
 * The actual BIOS layout has been mostly reverse engineered.  Intel BIOS
 * updates for this board include 10 related (*.bio - &.bi9) binary files and
 * another separate (*.bbo) binary file.  The 10 files are 64k of data + a
 * small header.  If the headers are stripped off, the 10 64k files can be
 * concatenated into a 640k image.  This is your BIOS image, proper.  The
 * separate .bbo file also has a small header.  It is the 'Boot Block'
 * recovery BIOS.  Once the header is stripped, no further prep is needed.
 * As best I can tell, the BIOS is arranged as such:
 * offset 0x00000 to 0x4ffff (320k):  unknown - SCSI BIOS, etc?
 * offset 0x50000 to 0xeffff (640k):  BIOS proper
 * offset 0xf0000 ty 0xfffff (64k):   Boot Block region
 *
 * Intel's BIOS update program flashes the BIOS and Boot Block in separate
 * steps.  Probably a wise thing to do.
 */

#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <asm/io.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/map.h>
#include <linux/mtd/cfi.h>
#include <linux/pci.h>
#include <linux/pci_ids.h>

#define MODNAME		"scb2_flash"
#define SCB2_ADDR	0xfff00000
#define SCB2_WINDOW	0x00100000


static void __iomem *scb2_ioaddr;
static struct mtd_info *scb2_mtd;
static struct map_info scb2_map = {
	.name =      "SCB2 BIOS Flash",
	.size =      0,
	.bankwidth =  1,
};
static int region_fail;

static int __devinit
scb2_fixup_mtd(struct mtd_info *mtd)
{
	int i;
	int done = 0;
	struct map_info *map = mtd->priv;
	struct cfi_private *cfi = map->fldrv_priv;

	/* barf if this doesn't look right */
	if (cfi->cfiq->InterfaceDesc != CFI_INTERFACE_X16_ASYNC) {
		printk(KERN_ERR MODNAME ": unsupported InterfaceDesc: %#x\n",
		    cfi->cfiq->InterfaceDesc);
		return -1;
	}

	/* I wasn't here. I didn't see. dwmw2. */

	/* the chip is sometimes bigger than the map - what a waste */
	mtd->size = map->size;

	/*
	 * We only REALLY get half the chip, due to the way it is
	 * wired up - D8-D15 are tossed away.  We read linear bytes,
	 * but in reality we are getting 1/2 of each 16-bit read,
	 * which LOOKS linear to us.  Because CFI code accounts for
	 * things like lock/unlock/erase by eraseregions, we need to
	 * fudge them to reflect this.  Erases go like this:
	 *   * send an erase to an address
	 *   * the chip samples the address and erases the block
	 *   * add the block erasesize to the address and repeat
	 *   -- the problem is that addresses are 16-bit addressable
	 *   -- we end up erasing every-other block
	 */
	mtd->erasesize /= 2;
	for (i = 0; i < mtd->numeraseregions; i++) {
		struct mtd_erase_region_info *region = &mtd->eraseregions[i];
		region->erasesize /= 2;
	}

	/*
	 * If the chip is bigger than the map, it is wired with the high
	 * address lines pulled up.  This makes us access the top portion of
	 * the chip, so all our erase-region info is wrong.  Start cutting from
	 * the bottom.
	 */
	for (i = 0; !done && i < mtd->numeraseregions; i++) {
		struct mtd_erase_region_info *region = &mtd->eraseregions[i];

		if (region->numblocks * region->erasesize > mtd->size) {
			region->numblocks = ((unsigned long)mtd->size /
						region->erasesize);
			done = 1;
		} else {
			region->numblocks = 0;
		}
		region->offset = 0;
	}

	return 0;
}

/* CSB5's 'Function Control Register' has bits for decoding @ >= 0xffc00000 */
#define CSB5_FCR	0x41
#define CSB5_FCR_DECODE_ALL 0x0e
static int __devinit
scb2_flash_probe(struct pci_dev *dev, const struct pci_device_id *ent)
{
	u8 reg;

	/* enable decoding of the flash region in the south bridge */
	pci_read_config_byte(dev, CSB5_FCR, &reg);
	pci_write_config_byte(dev, CSB5_FCR, reg | CSB5_FCR_DECODE_ALL);

	if (!request_mem_region(SCB2_ADDR, SCB2_WINDOW, scb2_map.name)) {
		/*
		 * The BIOS seems to mark the flash region as 'reserved'
		 * in the e820 map.  Warn and go about our business.
		 */
		printk(KERN_WARNING MODNAME
		    ": warning - can't reserve rom window, continuing\n");
		region_fail = 1;
	}

	/* remap the IO window (w/o caching) */
	scb2_ioaddr = ioremap_nocache(SCB2_ADDR, SCB2_WINDOW);
	if (!scb2_ioaddr) {
		printk(KERN_ERR MODNAME ": Failed to ioremap window!\n");
		if (!region_fail)
			release_mem_region(SCB2_ADDR, SCB2_WINDOW);
		return -ENOMEM;
	}

	scb2_map.phys = SCB2_ADDR;
	scb2_map.virt = scb2_ioaddr;
	scb2_map.size = SCB2_WINDOW;

	simple_map_init(&scb2_map);

	/* try to find a chip */
	scb2_mtd = do_map_probe("cfi_probe", &scb2_map);

	if (!scb2_mtd) {
		printk(KERN_ERR MODNAME ": flash probe failed!\n");
		iounmap(scb2_ioaddr);
		if (!region_fail)
			release_mem_region(SCB2_ADDR, SCB2_WINDOW);
		return -ENODEV;
	}

	scb2_mtd->owner = THIS_MODULE;
	if (scb2_fixup_mtd(scb2_mtd) < 0) {
		del_mtd_device(scb2_mtd);
		map_destroy(scb2_mtd);
		iounmap(scb2_ioaddr);
		if (!region_fail)
			release_mem_region(SCB2_ADDR, SCB2_WINDOW);
		return -ENODEV;
	}

	printk(KERN_NOTICE MODNAME ": chip size 0x%llx at offset 0x%llx\n",
	       (unsigned long long)scb2_mtd->size,
	       (unsigned long long)(SCB2_WINDOW - scb2_mtd->size));

	add_mtd_device(scb2_mtd);

	return 0;
}

static void __devexit
scb2_flash_remove(struct pci_dev *dev)
{
	if (!scb2_mtd)
		return;

	/* disable flash writes */
	if (scb2_mtd->lock)
		scb2_mtd->lock(scb2_mtd, 0, scb2_mtd->size);

	del_mtd_device(scb2_mtd);
	map_destroy(scb2_mtd);

	iounmap(scb2_ioaddr);
	scb2_ioaddr = NULL;

	if (!region_fail)
		release_mem_region(SCB2_ADDR, SCB2_WINDOW);
	pci_set_drvdata(dev, NULL);
}

static struct pci_device_id scb2_flash_pci_ids[] = {
	{
	  .vendor = PCI_VENDOR_ID_SERVERWORKS,
	  .device = PCI_DEVICE_ID_SERVERWORKS_CSB5,
	  .subvendor = PCI_ANY_ID,
	  .subdevice = PCI_ANY_ID
	},
	{ 0, }
};

static struct pci_driver scb2_flash_driver = {
	.name =     "Intel SCB2 BIOS Flash",
	.id_table = scb2_flash_pci_ids,
	.probe =    scb2_flash_probe,
	.remove =   __devexit_p(scb2_flash_remove),
};

static int __init
scb2_flash_init(void)
{
	return pci_register_driver(&scb2_flash_driver);
}

static void __exit
scb2_flash_exit(void)
{
	pci_unregister_driver(&scb2_flash_driver);
}

module_init(scb2_flash_init);
module_exit(scb2_flash_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Tim Hockin <thockin@sun.com>");
MODULE_DESCRIPTION("MTD map driver for Intel SCB2 BIOS Flash");
MODULE_DEVICE_TABLE(pci, scb2_flash_pci_ids);
OpenPOWER on IntegriCloud