aboutsummaryrefslogtreecommitdiff
path: root/drivers/mtd/maps/pismo.c
blob: 60c068db452d8678718790947340464ad61acc58 (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
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
/*
 * PISMO memory driver - http://www.pismoworld.org/
 *
 * For ARM Realview and Versatile platforms
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License.
 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#include <linux/mutex.h>
#include <linux/mtd/physmap.h>
#include <linux/mtd/plat-ram.h>
#include <linux/mtd/pismo.h>

#define PISMO_NUM_CS	5

struct pismo_cs_block {
	u8	type;
	u8	width;
	__le16	access;
	__le32	size;
	u32	reserved[2];
	char	device[32];
} __packed;

struct pismo_eeprom {
	struct pismo_cs_block cs[PISMO_NUM_CS];
	char	board[15];
	u8	sum;
} __packed;

struct pismo_mem {
	phys_addr_t base;
	u32	size;
	u16	access;
	u8	width;
	u8	type;
};

struct pismo_data {
	struct i2c_client	*client;
	void			(*vpp)(void *, int);
	void			*vpp_data;
	struct platform_device	*dev[PISMO_NUM_CS];
};

/* FIXME: set_vpp could do with a better calling convention */
static struct pismo_data *vpp_pismo;
static DEFINE_MUTEX(pismo_mutex);

static int pismo_setvpp_probe_fix(struct pismo_data *pismo)
{
	mutex_lock(&pismo_mutex);
	if (vpp_pismo) {
		mutex_unlock(&pismo_mutex);
		kfree(pismo);
		return -EBUSY;
	}
	vpp_pismo = pismo;
	mutex_unlock(&pismo_mutex);
	return 0;
}

static void pismo_setvpp_remove_fix(struct pismo_data *pismo)
{
	mutex_lock(&pismo_mutex);
	if (vpp_pismo == pismo)
		vpp_pismo = NULL;
	mutex_unlock(&pismo_mutex);
}

static void pismo_set_vpp(struct map_info *map, int on)
{
	struct pismo_data *pismo = vpp_pismo;

	pismo->vpp(pismo->vpp_data, on);
}
/* end of hack */


static unsigned int __devinit pismo_width_to_bytes(unsigned int width)
{
	width &= 15;
	if (width > 2)
		return 0;
	return 1 << width;
}

static int __devinit pismo_eeprom_read(struct i2c_client *client, void *buf,
	u8 addr, size_t size)
{
	int ret;
	struct i2c_msg msg[] = {
		{
			.addr = client->addr,
			.len = sizeof(addr),
			.buf = &addr,
		}, {
			.addr = client->addr,
			.flags = I2C_M_RD,
			.len = size,
			.buf = buf,
		},
	};

	ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));

	return ret == ARRAY_SIZE(msg) ? size : -EIO;
}

static int __devinit pismo_add_device(struct pismo_data *pismo, int i,
	struct pismo_mem *region, const char *name, void *pdata, size_t psize)
{
	struct platform_device *dev;
	struct resource res = { };
	phys_addr_t base = region->base;
	int ret;

	if (base == ~0)
		return -ENXIO;

	res.start = base;
	res.end = base + region->size - 1;
	res.flags = IORESOURCE_MEM;

	dev = platform_device_alloc(name, i);
	if (!dev)
		return -ENOMEM;
	dev->dev.parent = &pismo->client->dev;

	do {
		ret = platform_device_add_resources(dev, &res, 1);
		if (ret)
			break;

		ret = platform_device_add_data(dev, pdata, psize);
		if (ret)
			break;

		ret = platform_device_add(dev);
		if (ret)
			break;

		pismo->dev[i] = dev;
		return 0;
	} while (0);

	platform_device_put(dev);
	return ret;
}

static int __devinit pismo_add_nor(struct pismo_data *pismo, int i,
	struct pismo_mem *region)
{
	struct physmap_flash_data data = {
		.width = region->width,
	};

	if (pismo->vpp)
		data.set_vpp = pismo_set_vpp;

	return pismo_add_device(pismo, i, region, "physmap-flash",
		&data, sizeof(data));
}

static int __devinit pismo_add_sram(struct pismo_data *pismo, int i,
	struct pismo_mem *region)
{
	struct platdata_mtd_ram data = {
		.bankwidth = region->width,
	};

	return pismo_add_device(pismo, i, region, "mtd-ram",
		&data, sizeof(data));
}

static void __devinit pismo_add_one(struct pismo_data *pismo, int i,
	const struct pismo_cs_block *cs, phys_addr_t base)
{
	struct device *dev = &pismo->client->dev;
	struct pismo_mem region;

	region.base = base;
	region.type = cs->type;
	region.width = pismo_width_to_bytes(cs->width);
	region.access = le16_to_cpu(cs->access);
	region.size = le32_to_cpu(cs->size);

	if (region.width == 0) {
		dev_err(dev, "cs%u: bad width: %02x, ignoring\n", i, cs->width);
		return;
	}

	/*
	 * FIXME: may need to the platforms memory controller here, but at
	 * the moment we assume that it has already been correctly setup.
	 * The memory controller can also tell us the base address as well.
	 */

	dev_info(dev, "cs%u: %.32s: type %02x access %u00ps size %uK\n",
		i, cs->device, region.type, region.access, region.size / 1024);

	switch (region.type) {
	case 0:
		break;
	case 1:
		/* static DOC */
		break;
	case 2:
		/* static NOR */
		pismo_add_nor(pismo, i, &region);
		break;
	case 3:
		/* static RAM */
		pismo_add_sram(pismo, i, &region);
		break;
	}
}

static int __devexit pismo_remove(struct i2c_client *client)
{
	struct pismo_data *pismo = i2c_get_clientdata(client);
	int i;

	for (i = 0; i < ARRAY_SIZE(pismo->dev); i++)
		platform_device_unregister(pismo->dev[i]);

	/* FIXME: set_vpp needs saner arguments */
	pismo_setvpp_remove_fix(pismo);

	kfree(pismo);

	return 0;
}

static int __devinit pismo_probe(struct i2c_client *client,
				 const struct i2c_device_id *id)
{
	struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
	struct pismo_pdata *pdata = client->dev.platform_data;
	struct pismo_eeprom eeprom;
	struct pismo_data *pismo;
	int ret, i;

	if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) {
		dev_err(&client->dev, "functionality mismatch\n");
		return -EIO;
	}

	pismo = kzalloc(sizeof(*pismo), GFP_KERNEL);
	if (!pismo)
		return -ENOMEM;

	/* FIXME: set_vpp needs saner arguments */
	ret = pismo_setvpp_probe_fix(pismo);
	if (ret)
		return ret;

	pismo->client = client;
	if (pdata) {
		pismo->vpp = pdata->set_vpp;
		pismo->vpp_data = pdata->vpp_data;
	}
	i2c_set_clientdata(client, pismo);

	ret = pismo_eeprom_read(client, &eeprom, 0, sizeof(eeprom));
	if (ret < 0) {
		dev_err(&client->dev, "error reading EEPROM: %d\n", ret);
		return ret;
	}

	dev_info(&client->dev, "%.15s board found\n", eeprom.board);

	for (i = 0; i < ARRAY_SIZE(eeprom.cs); i++)
		if (eeprom.cs[i].type != 0xff)
			pismo_add_one(pismo, i, &eeprom.cs[i],
				      pdata->cs_addrs[i]);

	return 0;
}

static const struct i2c_device_id pismo_id[] = {
	{ "pismo" },
	{ },
};
MODULE_DEVICE_TABLE(i2c, pismo_id);

static struct i2c_driver pismo_driver = {
	.driver	= {
		.name	= "pismo",
		.owner	= THIS_MODULE,
	},
	.probe		= pismo_probe,
	.remove		= __devexit_p(pismo_remove),
	.id_table	= pismo_id,
};

static int __init pismo_init(void)
{
	BUILD_BUG_ON(sizeof(struct pismo_cs_block) != 48);
	BUILD_BUG_ON(sizeof(struct pismo_eeprom) != 256);

	return i2c_add_driver(&pismo_driver);
}
module_init(pismo_init);

static void __exit pismo_exit(void)
{
	i2c_del_driver(&pismo_driver);
}
module_exit(pismo_exit);

MODULE_AUTHOR("Russell King <linux@arm.linux.org.uk>");
MODULE_DESCRIPTION("PISMO memory driver");
MODULE_LICENSE("GPL");