summaryrefslogtreecommitdiffstats
path: root/uClinux-2.4.20-uc1/drivers/mtd/maps/ich2rom.c
blob: 8c174131b0b7c5ebff2b948f3330df894f0a64dc (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
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
/*
 * ich2rom.c
 *
 * Normal mappings of chips in physical memory
 * $Id: ich2rom.c,v 1.1 2002/01/10 22:59:13 eric Exp $
 */

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

#define RESERVE_MEM_REGION 0

#define ICH2_FWH_REGION_START	0xFF000000UL
#define ICH2_FWH_REGION_SIZE	0x01000000UL
#define BIOS_CNTL	0x4e
#define FWH_DEC_EN1	0xE3
#define FWH_DEC_EN2	0xF0
#define FWH_SEL1	0xE8
#define FWH_SEL2	0xEE

struct ich2rom_map_info {
	struct map_info map;
	struct mtd_info *mtd;
	unsigned long window_addr;
};

static inline unsigned long addr(struct map_info *map, unsigned long ofs)
{
	unsigned long offset;
	offset = ((8*1024*1024) - map->size) + ofs;
	if (offset >= (4*1024*1024)) {
		offset += 0x400000;
	}
	return map->map_priv_1 + 0x400000 + offset;
}

static inline unsigned long dbg_addr(struct map_info *map, unsigned long addr)
{
	return addr - map->map_priv_1 + ICH2_FWH_REGION_START;
}
	
static __u8 ich2rom_read8(struct map_info *map, unsigned long ofs)
{
	return __raw_readb(addr(map, ofs));
}

static __u16 ich2rom_read16(struct map_info *map, unsigned long ofs)
{
	return __raw_readw(addr(map, ofs));
}

static __u32 ich2rom_read32(struct map_info *map, unsigned long ofs)
{
	return __raw_readl(addr(map, ofs));
}

static void ich2rom_copy_from(struct map_info *map, void *to, unsigned long from, ssize_t len)
{
	memcpy_fromio(to, addr(map, from), len);
}

static void ich2rom_write8(struct map_info *map, __u8 d, unsigned long ofs)
{
	__raw_writeb(d, addr(map,ofs));
	mb();
}

static void ich2rom_write16(struct map_info *map, __u16 d, unsigned long ofs)
{
	__raw_writew(d, addr(map, ofs));
	mb();
}

static void ich2rom_write32(struct map_info *map, __u32 d, unsigned long ofs)
{
	__raw_writel(d, addr(map, ofs));
	mb();
}

static void ich2rom_copy_to(struct map_info *map, unsigned long to, const void *from, ssize_t len)
{
	memcpy_toio(addr(map, to), from, len);
}

static struct ich2rom_map_info ich2rom_map = {
	map: {
		name: "ICH2 rom",
		size: 0,
		buswidth: 1,
		read8: ich2rom_read8,
		read16: ich2rom_read16,
		read32: ich2rom_read32,
		copy_from: ich2rom_copy_from,
		write8: ich2rom_write8,
		write16: ich2rom_write16,
		write32: ich2rom_write32,
		copy_to: ich2rom_copy_to,
		/* Firmware hubs only use vpp when being programmed
		 * in a factory setting.  So in place programming
		 * needs to use a different method.
		 */
	},
	mtd: 0,
	window_addr: 0,
};

enum fwh_lock_state {
	FWH_DENY_WRITE = 1,
	FWH_IMMUTABLE  = 2,
	FWH_DENY_READ  = 4,
};

static int ich2rom_set_lock_state(struct mtd_info *mtd, loff_t ofs, size_t len,
	enum fwh_lock_state state)
{
	struct map_info *map = mtd->priv;
	unsigned long start = ofs;
	unsigned long end = start + len -1;

	/* FIXME do I need to guard against concurrency here? */
	/* round down to 64K boundaries */
	start = start & ~0xFFFF;
	end = end & ~0xFFFF;
	while (start <= end) {
		unsigned long ctrl_addr;
		ctrl_addr = addr(map, start) - 0x400000 + 2;
		writeb(state, ctrl_addr);
		start = start + 0x10000;
	}
	return 0;
}

static int ich2rom_lock(struct mtd_info *mtd, loff_t ofs, size_t len)
{
	return ich2rom_set_lock_state(mtd, ofs, len, FWH_DENY_WRITE);
}

static int ich2rom_unlock(struct mtd_info *mtd, loff_t ofs, size_t len)
{
	return ich2rom_set_lock_state(mtd, ofs, len, 0);
}

static int __devinit ich2rom_init_one (struct pci_dev *pdev,
	const struct pci_device_id *ent)
{
	u16 word;
	struct ich2rom_map_info *info = &ich2rom_map;
	unsigned long map_size;

	/* For now I just handle the ich2 and I assume there
	 * are not a lot of resources up at the top of the address
	 * space.  It is possible to handle other devices in the
	 * top 16MB but it is very painful.  Also since
	 * you can only really attach a FWH to an ICH2 there
	 * a number of simplifications you can make.
	 *
	 * Also you can page firmware hubs if an 8MB window isn't enough 
	 * but don't currently handle that case either.
	 */

#if RESERVE_MEM_REGION
	/* Some boards have this reserved and I haven't found a good work
	 * around to say I know what I'm doing!
	 */
	if (!request_mem_region(ICH2_FWH_REGION_START, ICH2_FWH_REGION_SIZE, "ich2rom")) {
		printk(KERN_ERR "ich2rom: cannot reserve rom window\n");
		goto err_out_none;
	}
#endif /* RESERVE_MEM_REGION */
	
	/* Enable writes through the rom window */
	pci_read_config_word(pdev, BIOS_CNTL, &word);
	if (!(word & 1)  && (word & (1<<1))) {
		/* The BIOS will generate an error if I enable
		 * this device, so don't even try.
		 */
		printk(KERN_ERR "ich2rom: firmware access control, I can't enable writes\n");
		goto err_out_none;
	}
	pci_write_config_word(pdev, BIOS_CNTL, word | 1);


	/* Map the firmware hub into my address space. */
	/* Does this use to much virtual address space? */
	info->window_addr = (unsigned long)ioremap(
		ICH2_FWH_REGION_START, ICH2_FWH_REGION_SIZE);
	if (!info->window_addr) {
		printk(KERN_ERR "Failed to ioremap\n");
		goto err_out_free_mmio_region;
	}

	/* For now assume the firmware has setup all relavent firmware
	 * windows.  We don't have enough information to handle this case
	 * intelligently.
	 */

	/* FIXME select the firmware hub and enable a window to it. */

	info->mtd = 0;
	info->map.map_priv_1 = 	info->window_addr;

	map_size = ICH2_FWH_REGION_SIZE;
	while(!info->mtd && (map_size > 0)) {
		info->map.size = map_size;
		info->mtd = do_map_probe("jedec_probe", &ich2rom_map.map);
		map_size -= 512*1024;
	}
	if (!info->mtd) {
		goto err_out_iounmap;
	}
	/* I know I can only be a firmware hub here so put
	 * in the special lock and unlock routines.
	 */
	info->mtd->lock = ich2rom_lock;
	info->mtd->unlock = ich2rom_unlock;
		
	info->mtd->module = THIS_MODULE;
	add_mtd_device(info->mtd);
	return 0;

err_out_iounmap:
	iounmap((void *)(info->window_addr));
err_out_free_mmio_region:
#if RESERVE_MEM_REGION
	release_mem_region(ICH2_FWH_REGION_START, ICH2_FWH_REGION_SIZE);
#endif
err_out_none:
	return -ENODEV;
}


static void __devexit ich2rom_remove_one (struct pci_dev *pdev)
{
	struct ich2rom_map_info *info = &ich2rom_map;
	u16 word;

	del_mtd_device(info->mtd);
	map_destroy(info->mtd);
	info->mtd = 0;
	info->map.map_priv_1 = 0;

	iounmap((void *)(info->window_addr));
	info->window_addr = 0;

	/* Disable writes through the rom window */
	pci_read_config_word(pdev, BIOS_CNTL, &word);
	pci_write_config_word(pdev, BIOS_CNTL, word & ~1);

#if RESERVE_MEM_REGION	
	release_mem_region(ICH2_FWH_REGION_START, ICH2_FWH_REGION_SIZE);
#endif
}

static struct pci_device_id ich2rom_pci_tbl[] __devinitdata = {
	{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0, 
	  PCI_ANY_ID, PCI_ANY_ID, },
};

MODULE_DEVICE_TABLE(pci, ich2rom_pci_tbl);

#if 0
static struct pci_driver ich2rom_driver = {
	name:	  "ich2rom",
	id_table: ich2rom_pci_tbl,
	probe:    ich2rom_init_one,
	remove:   ich2rom_remove_one,
};
#endif

static struct pci_dev *mydev;
int __init init_ich2rom(void)
{
	struct pci_dev *pdev;
	pdev = pci_find_device(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0, 0);
	if (pdev) {
		mydev = pdev;
		return ich2rom_init_one(pdev, &ich2rom_pci_tbl[0]);
	}
	return -ENXIO;
#if 0
	return pci_module_init(&ich2rom_driver);
#endif
}

static void __exit cleanup_ich2rom(void)
{
	ich2rom_remove_one(mydev);
}

module_init(init_ich2rom);
module_exit(cleanup_ich2rom);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Eric Biederman <ebiederman@lnxi.com>");
MODULE_DESCRIPTION("MTD map driver for BIOS chips on the ICH2 southbridge");