diff options
author | Anatolij Gustschin <agust@denx.de> | 2011-07-25 20:13:27 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2011-07-25 23:57:16 -0400 |
commit | 06b4501e88ad10f02849a3f9d7408ed6ae15a53f (patch) | |
tree | 641a1c6044582b393ad4d877f6a9924cc495bfc0 /drivers/misc/eeprom | |
parent | 6e60c02e9d9427f59842192bdb123cbeaf8bc9a0 (diff) |
misc/eeprom: add driver for microwire 93xx46 EEPROMs
Add EEPROM driver for 93xx46 chips. It can also be used with spi_gpio
driver to access 93xx46 EEPROMs connected over GPIO lines. This driver
supports read/write/erase access to the EEPROM chips over sysfs files.
[rdunlap@xenotime.net: fix printk format]
Signed-off-by: Anatolij Gustschin <agust@denx.de>
Cc: Jonathan Cameron <jic23@cam.ac.uk>
Cc: Grant Likely <grant.likely@secretlab.ca>
Signed-off-by: Randy Dunlap <rdunlap@xenotime.net>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/misc/eeprom')
-rw-r--r-- | drivers/misc/eeprom/Kconfig | 13 | ||||
-rw-r--r-- | drivers/misc/eeprom/Makefile | 1 | ||||
-rw-r--r-- | drivers/misc/eeprom/eeprom_93xx46.c | 410 |
3 files changed, 424 insertions, 0 deletions
diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig index 9118613af321..620de283b40b 100644 --- a/drivers/misc/eeprom/Kconfig +++ b/drivers/misc/eeprom/Kconfig | |||
@@ -70,4 +70,17 @@ config EEPROM_93CX6 | |||
70 | 70 | ||
71 | If unsure, say N. | 71 | If unsure, say N. |
72 | 72 | ||
73 | config EEPROM_93XX46 | ||
74 | tristate "Microwire EEPROM 93XX46 support" | ||
75 | depends on SPI && SYSFS | ||
76 | help | ||
77 | Driver for the microwire EEPROM chipsets 93xx46x. The driver | ||
78 | supports both read and write commands and also the command to | ||
79 | erase the whole EEPROM. | ||
80 | |||
81 | This driver can also be built as a module. If so, the module | ||
82 | will be called eeprom_93xx46. | ||
83 | |||
84 | If unsure, say N. | ||
85 | |||
73 | endmenu | 86 | endmenu |
diff --git a/drivers/misc/eeprom/Makefile b/drivers/misc/eeprom/Makefile index df3d68ffa9d1..ec55aad24cfb 100644 --- a/drivers/misc/eeprom/Makefile +++ b/drivers/misc/eeprom/Makefile | |||
@@ -3,3 +3,4 @@ obj-$(CONFIG_EEPROM_AT25) += at25.o | |||
3 | obj-$(CONFIG_EEPROM_LEGACY) += eeprom.o | 3 | obj-$(CONFIG_EEPROM_LEGACY) += eeprom.o |
4 | obj-$(CONFIG_EEPROM_MAX6875) += max6875.o | 4 | obj-$(CONFIG_EEPROM_MAX6875) += max6875.o |
5 | obj-$(CONFIG_EEPROM_93CX6) += eeprom_93cx6.o | 5 | obj-$(CONFIG_EEPROM_93CX6) += eeprom_93cx6.o |
6 | obj-$(CONFIG_EEPROM_93XX46) += eeprom_93xx46.o | ||
diff --git a/drivers/misc/eeprom/eeprom_93xx46.c b/drivers/misc/eeprom/eeprom_93xx46.c new file mode 100644 index 000000000000..0c7ebb1e19e5 --- /dev/null +++ b/drivers/misc/eeprom/eeprom_93xx46.c | |||
@@ -0,0 +1,410 @@ | |||
1 | /* | ||
2 | * Driver for 93xx46 EEPROMs | ||
3 | * | ||
4 | * (C) 2011 DENX Software Engineering, Anatolij Gustschin <agust@denx.de> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License version 2 as | ||
8 | * published by the Free Software Foundation. | ||
9 | */ | ||
10 | |||
11 | #include <linux/delay.h> | ||
12 | #include <linux/device.h> | ||
13 | #include <linux/kernel.h> | ||
14 | #include <linux/init.h> | ||
15 | #include <linux/module.h> | ||
16 | #include <linux/mutex.h> | ||
17 | #include <linux/slab.h> | ||
18 | #include <linux/spi/spi.h> | ||
19 | #include <linux/sysfs.h> | ||
20 | #include <linux/eeprom_93xx46.h> | ||
21 | |||
22 | #define OP_START 0x4 | ||
23 | #define OP_WRITE (OP_START | 0x1) | ||
24 | #define OP_READ (OP_START | 0x2) | ||
25 | #define ADDR_EWDS 0x00 | ||
26 | #define ADDR_ERAL 0x20 | ||
27 | #define ADDR_EWEN 0x30 | ||
28 | |||
29 | struct eeprom_93xx46_dev { | ||
30 | struct spi_device *spi; | ||
31 | struct eeprom_93xx46_platform_data *pdata; | ||
32 | struct bin_attribute bin; | ||
33 | struct mutex lock; | ||
34 | int addrlen; | ||
35 | }; | ||
36 | |||
37 | static ssize_t | ||
38 | eeprom_93xx46_bin_read(struct file *filp, struct kobject *kobj, | ||
39 | struct bin_attribute *bin_attr, | ||
40 | char *buf, loff_t off, size_t count) | ||
41 | { | ||
42 | struct eeprom_93xx46_dev *edev; | ||
43 | struct device *dev; | ||
44 | struct spi_message m; | ||
45 | struct spi_transfer t[2]; | ||
46 | int bits, ret; | ||
47 | u16 cmd_addr; | ||
48 | |||
49 | dev = container_of(kobj, struct device, kobj); | ||
50 | edev = dev_get_drvdata(dev); | ||
51 | |||
52 | if (unlikely(off >= edev->bin.size)) | ||
53 | return 0; | ||
54 | if ((off + count) > edev->bin.size) | ||
55 | count = edev->bin.size - off; | ||
56 | if (unlikely(!count)) | ||
57 | return count; | ||
58 | |||
59 | cmd_addr = OP_READ << edev->addrlen; | ||
60 | |||
61 | if (edev->addrlen == 7) { | ||
62 | cmd_addr |= off & 0x7f; | ||
63 | bits = 10; | ||
64 | } else { | ||
65 | cmd_addr |= off & 0x3f; | ||
66 | bits = 9; | ||
67 | } | ||
68 | |||
69 | dev_dbg(&edev->spi->dev, "read cmd 0x%x, %d Hz\n", | ||
70 | cmd_addr, edev->spi->max_speed_hz); | ||
71 | |||
72 | spi_message_init(&m); | ||
73 | memset(t, 0, sizeof(t)); | ||
74 | |||
75 | t[0].tx_buf = (char *)&cmd_addr; | ||
76 | t[0].len = 2; | ||
77 | t[0].bits_per_word = bits; | ||
78 | spi_message_add_tail(&t[0], &m); | ||
79 | |||
80 | t[1].rx_buf = buf; | ||
81 | t[1].len = count; | ||
82 | t[1].bits_per_word = 8; | ||
83 | spi_message_add_tail(&t[1], &m); | ||
84 | |||
85 | mutex_lock(&edev->lock); | ||
86 | |||
87 | if (edev->pdata->prepare) | ||
88 | edev->pdata->prepare(edev); | ||
89 | |||
90 | ret = spi_sync(edev->spi, &m); | ||
91 | /* have to wait at least Tcsl ns */ | ||
92 | ndelay(250); | ||
93 | if (ret) { | ||
94 | dev_err(&edev->spi->dev, "read %zu bytes at %d: err. %d\n", | ||
95 | count, (int)off, ret); | ||
96 | } | ||
97 | |||
98 | if (edev->pdata->finish) | ||
99 | edev->pdata->finish(edev); | ||
100 | |||
101 | mutex_unlock(&edev->lock); | ||
102 | return ret ? : count; | ||
103 | } | ||
104 | |||
105 | static int eeprom_93xx46_ew(struct eeprom_93xx46_dev *edev, int is_on) | ||
106 | { | ||
107 | struct spi_message m; | ||
108 | struct spi_transfer t; | ||
109 | int bits, ret; | ||
110 | u16 cmd_addr; | ||
111 | |||
112 | cmd_addr = OP_START << edev->addrlen; | ||
113 | if (edev->addrlen == 7) { | ||
114 | cmd_addr |= (is_on ? ADDR_EWEN : ADDR_EWDS) << 1; | ||
115 | bits = 10; | ||
116 | } else { | ||
117 | cmd_addr |= (is_on ? ADDR_EWEN : ADDR_EWDS); | ||
118 | bits = 9; | ||
119 | } | ||
120 | |||
121 | dev_dbg(&edev->spi->dev, "ew cmd 0x%04x\n", cmd_addr); | ||
122 | |||
123 | spi_message_init(&m); | ||
124 | memset(&t, 0, sizeof(t)); | ||
125 | |||
126 | t.tx_buf = &cmd_addr; | ||
127 | t.len = 2; | ||
128 | t.bits_per_word = bits; | ||
129 | spi_message_add_tail(&t, &m); | ||
130 | |||
131 | mutex_lock(&edev->lock); | ||
132 | |||
133 | if (edev->pdata->prepare) | ||
134 | edev->pdata->prepare(edev); | ||
135 | |||
136 | ret = spi_sync(edev->spi, &m); | ||
137 | /* have to wait at least Tcsl ns */ | ||
138 | ndelay(250); | ||
139 | if (ret) | ||
140 | dev_err(&edev->spi->dev, "erase/write %sable error %d\n", | ||
141 | is_on ? "en" : "dis", ret); | ||
142 | |||
143 | if (edev->pdata->finish) | ||
144 | edev->pdata->finish(edev); | ||
145 | |||
146 | mutex_unlock(&edev->lock); | ||
147 | return ret; | ||
148 | } | ||
149 | |||
150 | static ssize_t | ||
151 | eeprom_93xx46_write_word(struct eeprom_93xx46_dev *edev, | ||
152 | const char *buf, unsigned off) | ||
153 | { | ||
154 | struct spi_message m; | ||
155 | struct spi_transfer t[2]; | ||
156 | int bits, data_len, ret; | ||
157 | u16 cmd_addr; | ||
158 | |||
159 | cmd_addr = OP_WRITE << edev->addrlen; | ||
160 | |||
161 | if (edev->addrlen == 7) { | ||
162 | cmd_addr |= off & 0x7f; | ||
163 | bits = 10; | ||
164 | data_len = 1; | ||
165 | } else { | ||
166 | cmd_addr |= off & 0x3f; | ||
167 | bits = 9; | ||
168 | data_len = 2; | ||
169 | } | ||
170 | |||
171 | dev_dbg(&edev->spi->dev, "write cmd 0x%x\n", cmd_addr); | ||
172 | |||
173 | spi_message_init(&m); | ||
174 | memset(t, 0, sizeof(t)); | ||
175 | |||
176 | t[0].tx_buf = (char *)&cmd_addr; | ||
177 | t[0].len = 2; | ||
178 | t[0].bits_per_word = bits; | ||
179 | spi_message_add_tail(&t[0], &m); | ||
180 | |||
181 | t[1].tx_buf = buf; | ||
182 | t[1].len = data_len; | ||
183 | t[1].bits_per_word = 8; | ||
184 | spi_message_add_tail(&t[1], &m); | ||
185 | |||
186 | ret = spi_sync(edev->spi, &m); | ||
187 | /* have to wait program cycle time Twc ms */ | ||
188 | mdelay(6); | ||
189 | return ret; | ||
190 | } | ||
191 | |||
192 | static ssize_t | ||
193 | eeprom_93xx46_bin_write(struct file *filp, struct kobject *kobj, | ||
194 | struct bin_attribute *bin_attr, | ||
195 | char *buf, loff_t off, size_t count) | ||
196 | { | ||
197 | struct eeprom_93xx46_dev *edev; | ||
198 | struct device *dev; | ||
199 | int i, ret, step = 1; | ||
200 | |||
201 | dev = container_of(kobj, struct device, kobj); | ||
202 | edev = dev_get_drvdata(dev); | ||
203 | |||
204 | if (unlikely(off >= edev->bin.size)) | ||
205 | return 0; | ||
206 | if ((off + count) > edev->bin.size) | ||
207 | count = edev->bin.size - off; | ||
208 | if (unlikely(!count)) | ||
209 | return count; | ||
210 | |||
211 | /* only write even number of bytes on 16-bit devices */ | ||
212 | if (edev->addrlen == 6) { | ||
213 | step = 2; | ||
214 | count &= ~1; | ||
215 | } | ||
216 | |||
217 | /* erase/write enable */ | ||
218 | ret = eeprom_93xx46_ew(edev, 1); | ||
219 | if (ret) | ||
220 | return ret; | ||
221 | |||
222 | mutex_lock(&edev->lock); | ||
223 | |||
224 | if (edev->pdata->prepare) | ||
225 | edev->pdata->prepare(edev); | ||
226 | |||
227 | for (i = 0; i < count; i += step) { | ||
228 | ret = eeprom_93xx46_write_word(edev, &buf[i], off + i); | ||
229 | if (ret) { | ||
230 | dev_err(&edev->spi->dev, "write failed at %d: %d\n", | ||
231 | (int)off + i, ret); | ||
232 | break; | ||
233 | } | ||
234 | } | ||
235 | |||
236 | if (edev->pdata->finish) | ||
237 | edev->pdata->finish(edev); | ||
238 | |||
239 | mutex_unlock(&edev->lock); | ||
240 | |||
241 | /* erase/write disable */ | ||
242 | eeprom_93xx46_ew(edev, 0); | ||
243 | return ret ? : count; | ||
244 | } | ||
245 | |||
246 | static int eeprom_93xx46_eral(struct eeprom_93xx46_dev *edev) | ||
247 | { | ||
248 | struct eeprom_93xx46_platform_data *pd = edev->pdata; | ||
249 | struct spi_message m; | ||
250 | struct spi_transfer t; | ||
251 | int bits, ret; | ||
252 | u16 cmd_addr; | ||
253 | |||
254 | cmd_addr = OP_START << edev->addrlen; | ||
255 | if (edev->addrlen == 7) { | ||
256 | cmd_addr |= ADDR_ERAL << 1; | ||
257 | bits = 10; | ||
258 | } else { | ||
259 | cmd_addr |= ADDR_ERAL; | ||
260 | bits = 9; | ||
261 | } | ||
262 | |||
263 | spi_message_init(&m); | ||
264 | memset(&t, 0, sizeof(t)); | ||
265 | |||
266 | t.tx_buf = &cmd_addr; | ||
267 | t.len = 2; | ||
268 | t.bits_per_word = bits; | ||
269 | spi_message_add_tail(&t, &m); | ||
270 | |||
271 | mutex_lock(&edev->lock); | ||
272 | |||
273 | if (edev->pdata->prepare) | ||
274 | edev->pdata->prepare(edev); | ||
275 | |||
276 | ret = spi_sync(edev->spi, &m); | ||
277 | if (ret) | ||
278 | dev_err(&edev->spi->dev, "erase error %d\n", ret); | ||
279 | /* have to wait erase cycle time Tec ms */ | ||
280 | mdelay(6); | ||
281 | |||
282 | if (pd->finish) | ||
283 | pd->finish(edev); | ||
284 | |||
285 | mutex_unlock(&edev->lock); | ||
286 | return ret; | ||
287 | } | ||
288 | |||
289 | static ssize_t eeprom_93xx46_store_erase(struct device *dev, | ||
290 | struct device_attribute *attr, | ||
291 | const char *buf, size_t count) | ||
292 | { | ||
293 | struct eeprom_93xx46_dev *edev = dev_get_drvdata(dev); | ||
294 | int erase = 0, ret; | ||
295 | |||
296 | sscanf(buf, "%d", &erase); | ||
297 | if (erase) { | ||
298 | ret = eeprom_93xx46_ew(edev, 1); | ||
299 | if (ret) | ||
300 | return ret; | ||
301 | ret = eeprom_93xx46_eral(edev); | ||
302 | if (ret) | ||
303 | return ret; | ||
304 | ret = eeprom_93xx46_ew(edev, 0); | ||
305 | if (ret) | ||
306 | return ret; | ||
307 | } | ||
308 | return count; | ||
309 | } | ||
310 | static DEVICE_ATTR(erase, S_IWUSR, NULL, eeprom_93xx46_store_erase); | ||
311 | |||
312 | static int __devinit eeprom_93xx46_probe(struct spi_device *spi) | ||
313 | { | ||
314 | struct eeprom_93xx46_platform_data *pd; | ||
315 | struct eeprom_93xx46_dev *edev; | ||
316 | int err; | ||
317 | |||
318 | pd = spi->dev.platform_data; | ||
319 | if (!pd) { | ||
320 | dev_err(&spi->dev, "missing platform data\n"); | ||
321 | return -ENODEV; | ||
322 | } | ||
323 | |||
324 | edev = kzalloc(sizeof(*edev), GFP_KERNEL); | ||
325 | if (!edev) | ||
326 | return -ENOMEM; | ||
327 | |||
328 | if (pd->flags & EE_ADDR8) | ||
329 | edev->addrlen = 7; | ||
330 | else if (pd->flags & EE_ADDR16) | ||
331 | edev->addrlen = 6; | ||
332 | else { | ||
333 | dev_err(&spi->dev, "unspecified address type\n"); | ||
334 | err = -EINVAL; | ||
335 | goto fail; | ||
336 | } | ||
337 | |||
338 | mutex_init(&edev->lock); | ||
339 | |||
340 | edev->spi = spi_dev_get(spi); | ||
341 | edev->pdata = pd; | ||
342 | |||
343 | sysfs_bin_attr_init(&edev->bin); | ||
344 | edev->bin.attr.name = "eeprom"; | ||
345 | edev->bin.attr.mode = S_IRUSR; | ||
346 | edev->bin.read = eeprom_93xx46_bin_read; | ||
347 | edev->bin.size = 128; | ||
348 | if (!(pd->flags & EE_READONLY)) { | ||
349 | edev->bin.write = eeprom_93xx46_bin_write; | ||
350 | edev->bin.attr.mode |= S_IWUSR; | ||
351 | } | ||
352 | |||
353 | err = sysfs_create_bin_file(&spi->dev.kobj, &edev->bin); | ||
354 | if (err) | ||
355 | goto fail; | ||
356 | |||
357 | dev_info(&spi->dev, "%d-bit eeprom %s\n", | ||
358 | (pd->flags & EE_ADDR8) ? 8 : 16, | ||
359 | (pd->flags & EE_READONLY) ? "(readonly)" : ""); | ||
360 | |||
361 | if (!(pd->flags & EE_READONLY)) { | ||
362 | if (device_create_file(&spi->dev, &dev_attr_erase)) | ||
363 | dev_err(&spi->dev, "can't create erase interface\n"); | ||
364 | } | ||
365 | |||
366 | dev_set_drvdata(&spi->dev, edev); | ||
367 | return 0; | ||
368 | fail: | ||
369 | kfree(edev); | ||
370 | return err; | ||
371 | } | ||
372 | |||
373 | static int __devexit eeprom_93xx46_remove(struct spi_device *spi) | ||
374 | { | ||
375 | struct eeprom_93xx46_dev *edev = dev_get_drvdata(&spi->dev); | ||
376 | |||
377 | if (!(edev->pdata->flags & EE_READONLY)) | ||
378 | device_remove_file(&spi->dev, &dev_attr_erase); | ||
379 | |||
380 | sysfs_remove_bin_file(&spi->dev.kobj, &edev->bin); | ||
381 | dev_set_drvdata(&spi->dev, NULL); | ||
382 | kfree(edev); | ||
383 | return 0; | ||
384 | } | ||
385 | |||
386 | static struct spi_driver eeprom_93xx46_driver = { | ||
387 | .driver = { | ||
388 | .name = "93xx46", | ||
389 | .owner = THIS_MODULE, | ||
390 | }, | ||
391 | .probe = eeprom_93xx46_probe, | ||
392 | .remove = __devexit_p(eeprom_93xx46_remove), | ||
393 | }; | ||
394 | |||
395 | static int __init eeprom_93xx46_init(void) | ||
396 | { | ||
397 | return spi_register_driver(&eeprom_93xx46_driver); | ||
398 | } | ||
399 | module_init(eeprom_93xx46_init); | ||
400 | |||
401 | static void __exit eeprom_93xx46_exit(void) | ||
402 | { | ||
403 | spi_unregister_driver(&eeprom_93xx46_driver); | ||
404 | } | ||
405 | module_exit(eeprom_93xx46_exit); | ||
406 | |||
407 | MODULE_LICENSE("GPL"); | ||
408 | MODULE_DESCRIPTION("Driver for 93xx46 EEPROMs"); | ||
409 | MODULE_AUTHOR("Anatolij Gustschin <agust@denx.de>"); | ||
410 | MODULE_ALIAS("spi:93xx46"); | ||