diff options
author | Rodolfo Giometti <giometti@linux.it> | 2008-11-12 16:27:12 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2008-11-12 20:17:18 -0500 |
commit | 4e17e1db96474af5620e3259754df4cb1c46521c (patch) | |
tree | cc662ebf5158b407495a4939b0ea3d16b93a1b7e /drivers/misc/c2port | |
parent | e0a29382c6f51c278a7e9a788917ff9182f3dba6 (diff) |
Add c2 port support
C2port implements a two wire serial communication protocol (bit
banging) designed to enable in-system programming, debugging, and
boundary-scan testing on low pin-count Silicon Labs devices.
Currently this code supports only flash programming through sysfs
interface but extensions shoud be easy to add.
Signed-off-by: Rodolfo Giometti <giometti@linux.it>
Cc: Greg KH <greg@kroah.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/misc/c2port')
-rw-r--r-- | drivers/misc/c2port/Kconfig | 24 | ||||
-rw-r--r-- | drivers/misc/c2port/Makefile | 1 | ||||
-rw-r--r-- | drivers/misc/c2port/core.c | 1002 |
3 files changed, 1027 insertions, 0 deletions
diff --git a/drivers/misc/c2port/Kconfig b/drivers/misc/c2port/Kconfig new file mode 100644 index 000000000000..f1bad2b40329 --- /dev/null +++ b/drivers/misc/c2port/Kconfig | |||
@@ -0,0 +1,24 @@ | |||
1 | # | ||
2 | # C2 port devices | ||
3 | # | ||
4 | |||
5 | menuconfig C2PORT | ||
6 | tristate "Silicon Labs C2 port support (EXPERIMENTAL)" | ||
7 | depends on EXPERIMENTAL | ||
8 | default no | ||
9 | help | ||
10 | This option enables support for Silicon Labs C2 port used to | ||
11 | program Silicon micro controller chips (and other 8051 compatible). | ||
12 | |||
13 | If your board have no such micro controllers you don't need this | ||
14 | interface at all. | ||
15 | |||
16 | To compile this driver as a module, choose M here: the module will | ||
17 | be called c2port_core. Note that you also need a client module | ||
18 | usually called c2port-*. | ||
19 | |||
20 | If you are not sure, say N here. | ||
21 | |||
22 | if C2PORT | ||
23 | |||
24 | endif # C2PORT | ||
diff --git a/drivers/misc/c2port/Makefile b/drivers/misc/c2port/Makefile new file mode 100644 index 000000000000..3c610a2ba5ec --- /dev/null +++ b/drivers/misc/c2port/Makefile | |||
@@ -0,0 +1 @@ | |||
obj-$(CONFIG_C2PORT) += core.o | |||
diff --git a/drivers/misc/c2port/core.c b/drivers/misc/c2port/core.c new file mode 100644 index 000000000000..976b35d1d035 --- /dev/null +++ b/drivers/misc/c2port/core.c | |||
@@ -0,0 +1,1002 @@ | |||
1 | /* | ||
2 | * Silicon Labs C2 port core Linux support | ||
3 | * | ||
4 | * Copyright (c) 2007 Rodolfo Giometti <giometti@linux.it> | ||
5 | * Copyright (c) 2007 Eurotech S.p.A. <info@eurotech.it> | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify it | ||
8 | * under the terms of the GNU General Public License version 2 as published by | ||
9 | * the Free Software Foundation | ||
10 | */ | ||
11 | |||
12 | #include <linux/module.h> | ||
13 | #include <linux/init.h> | ||
14 | #include <linux/device.h> | ||
15 | #include <linux/errno.h> | ||
16 | #include <linux/err.h> | ||
17 | #include <linux/kernel.h> | ||
18 | #include <linux/ctype.h> | ||
19 | #include <linux/delay.h> | ||
20 | #include <linux/idr.h> | ||
21 | |||
22 | #include <linux/c2port.h> | ||
23 | |||
24 | #define DRIVER_NAME "c2port" | ||
25 | #define DRIVER_VERSION "0.51.0" | ||
26 | |||
27 | static DEFINE_SPINLOCK(c2port_idr_lock); | ||
28 | static DEFINE_IDR(c2port_idr); | ||
29 | |||
30 | /* | ||
31 | * Local variables | ||
32 | */ | ||
33 | |||
34 | static struct class *c2port_class; | ||
35 | |||
36 | /* | ||
37 | * C2 registers & commands defines | ||
38 | */ | ||
39 | |||
40 | /* C2 registers */ | ||
41 | #define C2PORT_DEVICEID 0x00 | ||
42 | #define C2PORT_REVID 0x01 | ||
43 | #define C2PORT_FPCTL 0x02 | ||
44 | #define C2PORT_FPDAT 0xB4 | ||
45 | |||
46 | /* C2 interface commands */ | ||
47 | #define C2PORT_GET_VERSION 0x01 | ||
48 | #define C2PORT_DEVICE_ERASE 0x03 | ||
49 | #define C2PORT_BLOCK_READ 0x06 | ||
50 | #define C2PORT_BLOCK_WRITE 0x07 | ||
51 | #define C2PORT_PAGE_ERASE 0x08 | ||
52 | |||
53 | /* C2 status return codes */ | ||
54 | #define C2PORT_INVALID_COMMAND 0x00 | ||
55 | #define C2PORT_COMMAND_FAILED 0x02 | ||
56 | #define C2PORT_COMMAND_OK 0x0d | ||
57 | |||
58 | /* | ||
59 | * C2 port low level signal managements | ||
60 | */ | ||
61 | |||
62 | static void c2port_reset(struct c2port_device *dev) | ||
63 | { | ||
64 | struct c2port_ops *ops = dev->ops; | ||
65 | |||
66 | /* To reset the device we have to keep clock line low for at least | ||
67 | * 20us. | ||
68 | */ | ||
69 | local_irq_disable(); | ||
70 | ops->c2ck_set(dev, 0); | ||
71 | udelay(25); | ||
72 | ops->c2ck_set(dev, 1); | ||
73 | local_irq_enable(); | ||
74 | |||
75 | udelay(1); | ||
76 | } | ||
77 | |||
78 | static void c2port_strobe_ck(struct c2port_device *dev) | ||
79 | { | ||
80 | struct c2port_ops *ops = dev->ops; | ||
81 | |||
82 | /* During hi-low-hi transition we disable local IRQs to avoid | ||
83 | * interructions since C2 port specification says that it must be | ||
84 | * shorter than 5us, otherwise the microcontroller may consider | ||
85 | * it as a reset signal! | ||
86 | */ | ||
87 | local_irq_disable(); | ||
88 | ops->c2ck_set(dev, 0); | ||
89 | udelay(1); | ||
90 | ops->c2ck_set(dev, 1); | ||
91 | local_irq_enable(); | ||
92 | |||
93 | udelay(1); | ||
94 | } | ||
95 | |||
96 | /* | ||
97 | * C2 port basic functions | ||
98 | */ | ||
99 | |||
100 | static void c2port_write_ar(struct c2port_device *dev, u8 addr) | ||
101 | { | ||
102 | struct c2port_ops *ops = dev->ops; | ||
103 | int i; | ||
104 | |||
105 | /* START field */ | ||
106 | c2port_strobe_ck(dev); | ||
107 | |||
108 | /* INS field (11b, LSB first) */ | ||
109 | ops->c2d_dir(dev, 0); | ||
110 | ops->c2d_set(dev, 1); | ||
111 | c2port_strobe_ck(dev); | ||
112 | ops->c2d_set(dev, 1); | ||
113 | c2port_strobe_ck(dev); | ||
114 | |||
115 | /* ADDRESS field */ | ||
116 | for (i = 0; i < 8; i++) { | ||
117 | ops->c2d_set(dev, addr & 0x01); | ||
118 | c2port_strobe_ck(dev); | ||
119 | |||
120 | addr >>= 1; | ||
121 | } | ||
122 | |||
123 | /* STOP field */ | ||
124 | ops->c2d_dir(dev, 1); | ||
125 | c2port_strobe_ck(dev); | ||
126 | } | ||
127 | |||
128 | static int c2port_read_ar(struct c2port_device *dev, u8 *addr) | ||
129 | { | ||
130 | struct c2port_ops *ops = dev->ops; | ||
131 | int i; | ||
132 | |||
133 | /* START field */ | ||
134 | c2port_strobe_ck(dev); | ||
135 | |||
136 | /* INS field (10b, LSB first) */ | ||
137 | ops->c2d_dir(dev, 0); | ||
138 | ops->c2d_set(dev, 0); | ||
139 | c2port_strobe_ck(dev); | ||
140 | ops->c2d_set(dev, 1); | ||
141 | c2port_strobe_ck(dev); | ||
142 | |||
143 | /* ADDRESS field */ | ||
144 | ops->c2d_dir(dev, 1); | ||
145 | *addr = 0; | ||
146 | for (i = 0; i < 8; i++) { | ||
147 | *addr >>= 1; /* shift in 8-bit ADDRESS field LSB first */ | ||
148 | |||
149 | c2port_strobe_ck(dev); | ||
150 | if (ops->c2d_get(dev)) | ||
151 | *addr |= 0x80; | ||
152 | } | ||
153 | |||
154 | /* STOP field */ | ||
155 | c2port_strobe_ck(dev); | ||
156 | |||
157 | return 0; | ||
158 | } | ||
159 | |||
160 | static int c2port_write_dr(struct c2port_device *dev, u8 data) | ||
161 | { | ||
162 | struct c2port_ops *ops = dev->ops; | ||
163 | int timeout, i; | ||
164 | |||
165 | /* START field */ | ||
166 | c2port_strobe_ck(dev); | ||
167 | |||
168 | /* INS field (01b, LSB first) */ | ||
169 | ops->c2d_dir(dev, 0); | ||
170 | ops->c2d_set(dev, 1); | ||
171 | c2port_strobe_ck(dev); | ||
172 | ops->c2d_set(dev, 0); | ||
173 | c2port_strobe_ck(dev); | ||
174 | |||
175 | /* LENGTH field (00b, LSB first -> 1 byte) */ | ||
176 | ops->c2d_set(dev, 0); | ||
177 | c2port_strobe_ck(dev); | ||
178 | ops->c2d_set(dev, 0); | ||
179 | c2port_strobe_ck(dev); | ||
180 | |||
181 | /* DATA field */ | ||
182 | for (i = 0; i < 8; i++) { | ||
183 | ops->c2d_set(dev, data & 0x01); | ||
184 | c2port_strobe_ck(dev); | ||
185 | |||
186 | data >>= 1; | ||
187 | } | ||
188 | |||
189 | /* WAIT field */ | ||
190 | ops->c2d_dir(dev, 1); | ||
191 | timeout = 20; | ||
192 | do { | ||
193 | c2port_strobe_ck(dev); | ||
194 | if (ops->c2d_get(dev)) | ||
195 | break; | ||
196 | |||
197 | udelay(1); | ||
198 | } while (--timeout > 0); | ||
199 | if (timeout == 0) | ||
200 | return -EIO; | ||
201 | |||
202 | /* STOP field */ | ||
203 | c2port_strobe_ck(dev); | ||
204 | |||
205 | return 0; | ||
206 | } | ||
207 | |||
208 | static int c2port_read_dr(struct c2port_device *dev, u8 *data) | ||
209 | { | ||
210 | struct c2port_ops *ops = dev->ops; | ||
211 | int timeout, i; | ||
212 | |||
213 | /* START field */ | ||
214 | c2port_strobe_ck(dev); | ||
215 | |||
216 | /* INS field (00b, LSB first) */ | ||
217 | ops->c2d_dir(dev, 0); | ||
218 | ops->c2d_set(dev, 0); | ||
219 | c2port_strobe_ck(dev); | ||
220 | ops->c2d_set(dev, 0); | ||
221 | c2port_strobe_ck(dev); | ||
222 | |||
223 | /* LENGTH field (00b, LSB first -> 1 byte) */ | ||
224 | ops->c2d_set(dev, 0); | ||
225 | c2port_strobe_ck(dev); | ||
226 | ops->c2d_set(dev, 0); | ||
227 | c2port_strobe_ck(dev); | ||
228 | |||
229 | /* WAIT field */ | ||
230 | ops->c2d_dir(dev, 1); | ||
231 | timeout = 20; | ||
232 | do { | ||
233 | c2port_strobe_ck(dev); | ||
234 | if (ops->c2d_get(dev)) | ||
235 | break; | ||
236 | |||
237 | udelay(1); | ||
238 | } while (--timeout > 0); | ||
239 | if (timeout == 0) | ||
240 | return -EIO; | ||
241 | |||
242 | /* DATA field */ | ||
243 | *data = 0; | ||
244 | for (i = 0; i < 8; i++) { | ||
245 | *data >>= 1; /* shift in 8-bit DATA field LSB first */ | ||
246 | |||
247 | c2port_strobe_ck(dev); | ||
248 | if (ops->c2d_get(dev)) | ||
249 | *data |= 0x80; | ||
250 | } | ||
251 | |||
252 | /* STOP field */ | ||
253 | c2port_strobe_ck(dev); | ||
254 | |||
255 | return 0; | ||
256 | } | ||
257 | |||
258 | static int c2port_poll_in_busy(struct c2port_device *dev) | ||
259 | { | ||
260 | u8 addr; | ||
261 | int ret, timeout = 20; | ||
262 | |||
263 | do { | ||
264 | ret = (c2port_read_ar(dev, &addr)); | ||
265 | if (ret < 0) | ||
266 | return -EIO; | ||
267 | |||
268 | if (!(addr & 0x02)) | ||
269 | break; | ||
270 | |||
271 | udelay(1); | ||
272 | } while (--timeout > 0); | ||
273 | if (timeout == 0) | ||
274 | return -EIO; | ||
275 | |||
276 | return 0; | ||
277 | } | ||
278 | |||
279 | static int c2port_poll_out_ready(struct c2port_device *dev) | ||
280 | { | ||
281 | u8 addr; | ||
282 | int ret, timeout = 10000; /* erase flash needs long time... */ | ||
283 | |||
284 | do { | ||
285 | ret = (c2port_read_ar(dev, &addr)); | ||
286 | if (ret < 0) | ||
287 | return -EIO; | ||
288 | |||
289 | if (addr & 0x01) | ||
290 | break; | ||
291 | |||
292 | udelay(1); | ||
293 | } while (--timeout > 0); | ||
294 | if (timeout == 0) | ||
295 | return -EIO; | ||
296 | |||
297 | return 0; | ||
298 | } | ||
299 | |||
300 | /* | ||
301 | * sysfs methods | ||
302 | */ | ||
303 | |||
304 | static ssize_t c2port_show_name(struct device *dev, | ||
305 | struct device_attribute *attr, char *buf) | ||
306 | { | ||
307 | struct c2port_device *c2dev = dev_get_drvdata(dev); | ||
308 | |||
309 | return sprintf(buf, "%s\n", c2dev->name); | ||
310 | } | ||
311 | |||
312 | static ssize_t c2port_show_flash_blocks_num(struct device *dev, | ||
313 | struct device_attribute *attr, char *buf) | ||
314 | { | ||
315 | struct c2port_device *c2dev = dev_get_drvdata(dev); | ||
316 | struct c2port_ops *ops = c2dev->ops; | ||
317 | |||
318 | return sprintf(buf, "%d\n", ops->blocks_num); | ||
319 | } | ||
320 | |||
321 | static ssize_t c2port_show_flash_block_size(struct device *dev, | ||
322 | struct device_attribute *attr, char *buf) | ||
323 | { | ||
324 | struct c2port_device *c2dev = dev_get_drvdata(dev); | ||
325 | struct c2port_ops *ops = c2dev->ops; | ||
326 | |||
327 | return sprintf(buf, "%d\n", ops->block_size); | ||
328 | } | ||
329 | |||
330 | static ssize_t c2port_show_flash_size(struct device *dev, | ||
331 | struct device_attribute *attr, char *buf) | ||
332 | { | ||
333 | struct c2port_device *c2dev = dev_get_drvdata(dev); | ||
334 | struct c2port_ops *ops = c2dev->ops; | ||
335 | |||
336 | return sprintf(buf, "%d\n", ops->blocks_num * ops->block_size); | ||
337 | } | ||
338 | |||
339 | static ssize_t c2port_show_access(struct device *dev, | ||
340 | struct device_attribute *attr, char *buf) | ||
341 | { | ||
342 | struct c2port_device *c2dev = dev_get_drvdata(dev); | ||
343 | |||
344 | return sprintf(buf, "%d\n", c2dev->access); | ||
345 | } | ||
346 | |||
347 | static ssize_t c2port_store_access(struct device *dev, | ||
348 | struct device_attribute *attr, | ||
349 | const char *buf, size_t count) | ||
350 | { | ||
351 | struct c2port_device *c2dev = dev_get_drvdata(dev); | ||
352 | struct c2port_ops *ops = c2dev->ops; | ||
353 | int status, ret; | ||
354 | |||
355 | ret = sscanf(buf, "%d", &status); | ||
356 | if (ret != 1) | ||
357 | return -EINVAL; | ||
358 | |||
359 | mutex_lock(&c2dev->mutex); | ||
360 | |||
361 | c2dev->access = !!status; | ||
362 | |||
363 | /* If access is "on" clock should be HIGH _before_ setting the line | ||
364 | * as output and data line should be set as INPUT anyway */ | ||
365 | if (c2dev->access) | ||
366 | ops->c2ck_set(c2dev, 1); | ||
367 | ops->access(c2dev, c2dev->access); | ||
368 | if (c2dev->access) | ||
369 | ops->c2d_dir(c2dev, 1); | ||
370 | |||
371 | mutex_unlock(&c2dev->mutex); | ||
372 | |||
373 | return count; | ||
374 | } | ||
375 | |||
376 | static ssize_t c2port_store_reset(struct device *dev, | ||
377 | struct device_attribute *attr, | ||
378 | const char *buf, size_t count) | ||
379 | { | ||
380 | struct c2port_device *c2dev = dev_get_drvdata(dev); | ||
381 | |||
382 | /* Check the device access status */ | ||
383 | if (!c2dev->access) | ||
384 | return -EBUSY; | ||
385 | |||
386 | mutex_lock(&c2dev->mutex); | ||
387 | |||
388 | c2port_reset(c2dev); | ||
389 | c2dev->flash_access = 0; | ||
390 | |||
391 | mutex_unlock(&c2dev->mutex); | ||
392 | |||
393 | return count; | ||
394 | } | ||
395 | |||
396 | static ssize_t __c2port_show_dev_id(struct c2port_device *dev, char *buf) | ||
397 | { | ||
398 | u8 data; | ||
399 | int ret; | ||
400 | |||
401 | /* Select DEVICEID register for C2 data register accesses */ | ||
402 | c2port_write_ar(dev, C2PORT_DEVICEID); | ||
403 | |||
404 | /* Read and return the device ID register */ | ||
405 | ret = c2port_read_dr(dev, &data); | ||
406 | if (ret < 0) | ||
407 | return ret; | ||
408 | |||
409 | return sprintf(buf, "%d\n", data); | ||
410 | } | ||
411 | |||
412 | static ssize_t c2port_show_dev_id(struct device *dev, | ||
413 | struct device_attribute *attr, char *buf) | ||
414 | { | ||
415 | struct c2port_device *c2dev = dev_get_drvdata(dev); | ||
416 | ssize_t ret; | ||
417 | |||
418 | /* Check the device access status */ | ||
419 | if (!c2dev->access) | ||
420 | return -EBUSY; | ||
421 | |||
422 | mutex_lock(&c2dev->mutex); | ||
423 | ret = __c2port_show_dev_id(c2dev, buf); | ||
424 | mutex_unlock(&c2dev->mutex); | ||
425 | |||
426 | if (ret < 0) | ||
427 | dev_err(dev, "cannot read from %s\n", c2dev->name); | ||
428 | |||
429 | return ret; | ||
430 | } | ||
431 | |||
432 | static ssize_t __c2port_show_rev_id(struct c2port_device *dev, char *buf) | ||
433 | { | ||
434 | u8 data; | ||
435 | int ret; | ||
436 | |||
437 | /* Select REVID register for C2 data register accesses */ | ||
438 | c2port_write_ar(dev, C2PORT_REVID); | ||
439 | |||
440 | /* Read and return the revision ID register */ | ||
441 | ret = c2port_read_dr(dev, &data); | ||
442 | if (ret < 0) | ||
443 | return ret; | ||
444 | |||
445 | return sprintf(buf, "%d\n", data); | ||
446 | } | ||
447 | |||
448 | static ssize_t c2port_show_rev_id(struct device *dev, | ||
449 | struct device_attribute *attr, char *buf) | ||
450 | { | ||
451 | struct c2port_device *c2dev = dev_get_drvdata(dev); | ||
452 | ssize_t ret; | ||
453 | |||
454 | /* Check the device access status */ | ||
455 | if (!c2dev->access) | ||
456 | return -EBUSY; | ||
457 | |||
458 | mutex_lock(&c2dev->mutex); | ||
459 | ret = __c2port_show_rev_id(c2dev, buf); | ||
460 | mutex_unlock(&c2dev->mutex); | ||
461 | |||
462 | if (ret < 0) | ||
463 | dev_err(c2dev->dev, "cannot read from %s\n", c2dev->name); | ||
464 | |||
465 | return ret; | ||
466 | } | ||
467 | |||
468 | static ssize_t c2port_show_flash_access(struct device *dev, | ||
469 | struct device_attribute *attr, char *buf) | ||
470 | { | ||
471 | struct c2port_device *c2dev = dev_get_drvdata(dev); | ||
472 | |||
473 | return sprintf(buf, "%d\n", c2dev->flash_access); | ||
474 | } | ||
475 | |||
476 | static ssize_t __c2port_store_flash_access(struct c2port_device *dev, | ||
477 | int status) | ||
478 | { | ||
479 | int ret; | ||
480 | |||
481 | /* Check the device access status */ | ||
482 | if (!dev->access) | ||
483 | return -EBUSY; | ||
484 | |||
485 | dev->flash_access = !!status; | ||
486 | |||
487 | /* If flash_access is off we have nothing to do... */ | ||
488 | if (dev->flash_access == 0) | ||
489 | return 0; | ||
490 | |||
491 | /* Target the C2 flash programming control register for C2 data | ||
492 | * register access */ | ||
493 | c2port_write_ar(dev, C2PORT_FPCTL); | ||
494 | |||
495 | /* Write the first keycode to enable C2 Flash programming */ | ||
496 | ret = c2port_write_dr(dev, 0x02); | ||
497 | if (ret < 0) | ||
498 | return ret; | ||
499 | |||
500 | /* Write the second keycode to enable C2 Flash programming */ | ||
501 | ret = c2port_write_dr(dev, 0x01); | ||
502 | if (ret < 0) | ||
503 | return ret; | ||
504 | |||
505 | /* Delay for at least 20ms to ensure the target is ready for | ||
506 | * C2 flash programming */ | ||
507 | mdelay(25); | ||
508 | |||
509 | return 0; | ||
510 | } | ||
511 | |||
512 | static ssize_t c2port_store_flash_access(struct device *dev, | ||
513 | struct device_attribute *attr, | ||
514 | const char *buf, size_t count) | ||
515 | { | ||
516 | struct c2port_device *c2dev = dev_get_drvdata(dev); | ||
517 | int status; | ||
518 | ssize_t ret; | ||
519 | |||
520 | ret = sscanf(buf, "%d", &status); | ||
521 | if (ret != 1) | ||
522 | return -EINVAL; | ||
523 | |||
524 | mutex_lock(&c2dev->mutex); | ||
525 | ret = __c2port_store_flash_access(c2dev, status); | ||
526 | mutex_unlock(&c2dev->mutex); | ||
527 | |||
528 | if (ret < 0) { | ||
529 | dev_err(c2dev->dev, "cannot enable %s flash programming\n", | ||
530 | c2dev->name); | ||
531 | return ret; | ||
532 | } | ||
533 | |||
534 | return count; | ||
535 | } | ||
536 | |||
537 | static ssize_t __c2port_write_flash_erase(struct c2port_device *dev) | ||
538 | { | ||
539 | u8 status; | ||
540 | int ret; | ||
541 | |||
542 | /* Target the C2 flash programming data register for C2 data register | ||
543 | * access. | ||
544 | */ | ||
545 | c2port_write_ar(dev, C2PORT_FPDAT); | ||
546 | |||
547 | /* Send device erase command */ | ||
548 | c2port_write_dr(dev, C2PORT_DEVICE_ERASE); | ||
549 | |||
550 | /* Wait for input acknowledge */ | ||
551 | ret = c2port_poll_in_busy(dev); | ||
552 | if (ret < 0) | ||
553 | return ret; | ||
554 | |||
555 | /* Should check status before starting FLASH access sequence */ | ||
556 | |||
557 | /* Wait for status information */ | ||
558 | ret = c2port_poll_out_ready(dev); | ||
559 | if (ret < 0) | ||
560 | return ret; | ||
561 | |||
562 | /* Read flash programming interface status */ | ||
563 | ret = c2port_read_dr(dev, &status); | ||
564 | if (ret < 0) | ||
565 | return ret; | ||
566 | if (status != C2PORT_COMMAND_OK) | ||
567 | return -EBUSY; | ||
568 | |||
569 | /* Send a three-byte arming sequence to enable the device erase. | ||
570 | * If the sequence is not received correctly, the command will be | ||
571 | * ignored. | ||
572 | * Sequence is: 0xde, 0xad, 0xa5. | ||
573 | */ | ||
574 | c2port_write_dr(dev, 0xde); | ||
575 | ret = c2port_poll_in_busy(dev); | ||
576 | if (ret < 0) | ||
577 | return ret; | ||
578 | c2port_write_dr(dev, 0xad); | ||
579 | ret = c2port_poll_in_busy(dev); | ||
580 | if (ret < 0) | ||
581 | return ret; | ||
582 | c2port_write_dr(dev, 0xa5); | ||
583 | ret = c2port_poll_in_busy(dev); | ||
584 | if (ret < 0) | ||
585 | return ret; | ||
586 | |||
587 | ret = c2port_poll_out_ready(dev); | ||
588 | if (ret < 0) | ||
589 | return ret; | ||
590 | |||
591 | return 0; | ||
592 | } | ||
593 | |||
594 | static ssize_t c2port_store_flash_erase(struct device *dev, | ||
595 | struct device_attribute *attr, | ||
596 | const char *buf, size_t count) | ||
597 | { | ||
598 | struct c2port_device *c2dev = dev_get_drvdata(dev); | ||
599 | int ret; | ||
600 | |||
601 | /* Check the device and flash access status */ | ||
602 | if (!c2dev->access || !c2dev->flash_access) | ||
603 | return -EBUSY; | ||
604 | |||
605 | mutex_lock(&c2dev->mutex); | ||
606 | ret = __c2port_write_flash_erase(c2dev); | ||
607 | mutex_unlock(&c2dev->mutex); | ||
608 | |||
609 | if (ret < 0) { | ||
610 | dev_err(c2dev->dev, "cannot erase %s flash\n", c2dev->name); | ||
611 | return ret; | ||
612 | } | ||
613 | |||
614 | return count; | ||
615 | } | ||
616 | |||
617 | static ssize_t __c2port_read_flash_data(struct c2port_device *dev, | ||
618 | char *buffer, loff_t offset, size_t count) | ||
619 | { | ||
620 | struct c2port_ops *ops = dev->ops; | ||
621 | u8 status, nread = 128; | ||
622 | int i, ret; | ||
623 | |||
624 | /* Check for flash end */ | ||
625 | if (offset >= ops->block_size * ops->blocks_num) | ||
626 | return 0; | ||
627 | |||
628 | if (ops->block_size * ops->blocks_num - offset < nread) | ||
629 | nread = ops->block_size * ops->blocks_num - offset; | ||
630 | if (count < nread) | ||
631 | nread = count; | ||
632 | if (nread == 0) | ||
633 | return nread; | ||
634 | |||
635 | /* Target the C2 flash programming data register for C2 data register | ||
636 | * access */ | ||
637 | c2port_write_ar(dev, C2PORT_FPDAT); | ||
638 | |||
639 | /* Send flash block read command */ | ||
640 | c2port_write_dr(dev, C2PORT_BLOCK_READ); | ||
641 | |||
642 | /* Wait for input acknowledge */ | ||
643 | ret = c2port_poll_in_busy(dev); | ||
644 | if (ret < 0) | ||
645 | return ret; | ||
646 | |||
647 | /* Should check status before starting FLASH access sequence */ | ||
648 | |||
649 | /* Wait for status information */ | ||
650 | ret = c2port_poll_out_ready(dev); | ||
651 | if (ret < 0) | ||
652 | return ret; | ||
653 | |||
654 | /* Read flash programming interface status */ | ||
655 | ret = c2port_read_dr(dev, &status); | ||
656 | if (ret < 0) | ||
657 | return ret; | ||
658 | if (status != C2PORT_COMMAND_OK) | ||
659 | return -EBUSY; | ||
660 | |||
661 | /* Send address high byte */ | ||
662 | c2port_write_dr(dev, offset >> 8); | ||
663 | ret = c2port_poll_in_busy(dev); | ||
664 | if (ret < 0) | ||
665 | return ret; | ||
666 | |||
667 | /* Send address low byte */ | ||
668 | c2port_write_dr(dev, offset & 0x00ff); | ||
669 | ret = c2port_poll_in_busy(dev); | ||
670 | if (ret < 0) | ||
671 | return ret; | ||
672 | |||
673 | /* Send address block size */ | ||
674 | c2port_write_dr(dev, nread); | ||
675 | ret = c2port_poll_in_busy(dev); | ||
676 | if (ret < 0) | ||
677 | return ret; | ||
678 | |||
679 | /* Should check status before reading FLASH block */ | ||
680 | |||
681 | /* Wait for status information */ | ||
682 | ret = c2port_poll_out_ready(dev); | ||
683 | if (ret < 0) | ||
684 | return ret; | ||
685 | |||
686 | /* Read flash programming interface status */ | ||
687 | ret = c2port_read_dr(dev, &status); | ||
688 | if (ret < 0) | ||
689 | return ret; | ||
690 | if (status != C2PORT_COMMAND_OK) | ||
691 | return -EBUSY; | ||
692 | |||
693 | /* Read flash block */ | ||
694 | for (i = 0; i < nread; i++) { | ||
695 | ret = c2port_poll_out_ready(dev); | ||
696 | if (ret < 0) | ||
697 | return ret; | ||
698 | |||
699 | ret = c2port_read_dr(dev, buffer+i); | ||
700 | if (ret < 0) | ||
701 | return ret; | ||
702 | } | ||
703 | |||
704 | return nread; | ||
705 | } | ||
706 | |||
707 | static ssize_t c2port_read_flash_data(struct kobject *kobj, | ||
708 | struct bin_attribute *attr, | ||
709 | char *buffer, loff_t offset, size_t count) | ||
710 | { | ||
711 | struct c2port_device *c2dev = | ||
712 | dev_get_drvdata(container_of(kobj, | ||
713 | struct device, kobj)); | ||
714 | ssize_t ret; | ||
715 | |||
716 | /* Check the device and flash access status */ | ||
717 | if (!c2dev->access || !c2dev->flash_access) | ||
718 | return -EBUSY; | ||
719 | |||
720 | mutex_lock(&c2dev->mutex); | ||
721 | ret = __c2port_read_flash_data(c2dev, buffer, offset, count); | ||
722 | mutex_unlock(&c2dev->mutex); | ||
723 | |||
724 | if (ret < 0) | ||
725 | dev_err(c2dev->dev, "cannot read %s flash\n", c2dev->name); | ||
726 | |||
727 | return ret; | ||
728 | } | ||
729 | |||
730 | static ssize_t __c2port_write_flash_data(struct c2port_device *dev, | ||
731 | char *buffer, loff_t offset, size_t count) | ||
732 | { | ||
733 | struct c2port_ops *ops = dev->ops; | ||
734 | u8 status, nwrite = 128; | ||
735 | int i, ret; | ||
736 | |||
737 | if (nwrite > count) | ||
738 | nwrite = count; | ||
739 | if (ops->block_size * ops->blocks_num - offset < nwrite) | ||
740 | nwrite = ops->block_size * ops->blocks_num - offset; | ||
741 | |||
742 | /* Check for flash end */ | ||
743 | if (offset >= ops->block_size * ops->blocks_num) | ||
744 | return -EINVAL; | ||
745 | |||
746 | /* Target the C2 flash programming data register for C2 data register | ||
747 | * access */ | ||
748 | c2port_write_ar(dev, C2PORT_FPDAT); | ||
749 | |||
750 | /* Send flash block write command */ | ||
751 | c2port_write_dr(dev, C2PORT_BLOCK_WRITE); | ||
752 | |||
753 | /* Wait for input acknowledge */ | ||
754 | ret = c2port_poll_in_busy(dev); | ||
755 | if (ret < 0) | ||
756 | return ret; | ||
757 | |||
758 | /* Should check status before starting FLASH access sequence */ | ||
759 | |||
760 | /* Wait for status information */ | ||
761 | ret = c2port_poll_out_ready(dev); | ||
762 | if (ret < 0) | ||
763 | return ret; | ||
764 | |||
765 | /* Read flash programming interface status */ | ||
766 | ret = c2port_read_dr(dev, &status); | ||
767 | if (ret < 0) | ||
768 | return ret; | ||
769 | if (status != C2PORT_COMMAND_OK) | ||
770 | return -EBUSY; | ||
771 | |||
772 | /* Send address high byte */ | ||
773 | c2port_write_dr(dev, offset >> 8); | ||
774 | ret = c2port_poll_in_busy(dev); | ||
775 | if (ret < 0) | ||
776 | return ret; | ||
777 | |||
778 | /* Send address low byte */ | ||
779 | c2port_write_dr(dev, offset & 0x00ff); | ||
780 | ret = c2port_poll_in_busy(dev); | ||
781 | if (ret < 0) | ||
782 | return ret; | ||
783 | |||
784 | /* Send address block size */ | ||
785 | c2port_write_dr(dev, nwrite); | ||
786 | ret = c2port_poll_in_busy(dev); | ||
787 | if (ret < 0) | ||
788 | return ret; | ||
789 | |||
790 | /* Should check status before writing FLASH block */ | ||
791 | |||
792 | /* Wait for status information */ | ||
793 | ret = c2port_poll_out_ready(dev); | ||
794 | if (ret < 0) | ||
795 | return ret; | ||
796 | |||
797 | /* Read flash programming interface status */ | ||
798 | ret = c2port_read_dr(dev, &status); | ||
799 | if (ret < 0) | ||
800 | return ret; | ||
801 | if (status != C2PORT_COMMAND_OK) | ||
802 | return -EBUSY; | ||
803 | |||
804 | /* Write flash block */ | ||
805 | for (i = 0; i < nwrite; i++) { | ||
806 | ret = c2port_write_dr(dev, *(buffer+i)); | ||
807 | if (ret < 0) | ||
808 | return ret; | ||
809 | |||
810 | ret = c2port_poll_in_busy(dev); | ||
811 | if (ret < 0) | ||
812 | return ret; | ||
813 | |||
814 | } | ||
815 | |||
816 | /* Wait for last flash write to complete */ | ||
817 | ret = c2port_poll_out_ready(dev); | ||
818 | if (ret < 0) | ||
819 | return ret; | ||
820 | |||
821 | return nwrite; | ||
822 | } | ||
823 | |||
824 | static ssize_t c2port_write_flash_data(struct kobject *kobj, | ||
825 | struct bin_attribute *attr, | ||
826 | char *buffer, loff_t offset, size_t count) | ||
827 | { | ||
828 | struct c2port_device *c2dev = | ||
829 | dev_get_drvdata(container_of(kobj, | ||
830 | struct device, kobj)); | ||
831 | int ret; | ||
832 | |||
833 | /* Check the device access status */ | ||
834 | if (!c2dev->access || !c2dev->flash_access) | ||
835 | return -EBUSY; | ||
836 | |||
837 | mutex_lock(&c2dev->mutex); | ||
838 | ret = __c2port_write_flash_data(c2dev, buffer, offset, count); | ||
839 | mutex_unlock(&c2dev->mutex); | ||
840 | |||
841 | if (ret < 0) | ||
842 | dev_err(c2dev->dev, "cannot write %s flash\n", c2dev->name); | ||
843 | |||
844 | return ret; | ||
845 | } | ||
846 | |||
847 | /* | ||
848 | * Class attributes | ||
849 | */ | ||
850 | |||
851 | static struct device_attribute c2port_attrs[] = { | ||
852 | __ATTR(name, 0444, c2port_show_name, NULL), | ||
853 | __ATTR(flash_blocks_num, 0444, c2port_show_flash_blocks_num, NULL), | ||
854 | __ATTR(flash_block_size, 0444, c2port_show_flash_block_size, NULL), | ||
855 | __ATTR(flash_size, 0444, c2port_show_flash_size, NULL), | ||
856 | __ATTR(access, 0644, c2port_show_access, c2port_store_access), | ||
857 | __ATTR(reset, 0200, NULL, c2port_store_reset), | ||
858 | __ATTR(dev_id, 0444, c2port_show_dev_id, NULL), | ||
859 | __ATTR(rev_id, 0444, c2port_show_rev_id, NULL), | ||
860 | |||
861 | __ATTR(flash_access, 0644, c2port_show_flash_access, | ||
862 | c2port_store_flash_access), | ||
863 | __ATTR(flash_erase, 0200, NULL, c2port_store_flash_erase), | ||
864 | __ATTR_NULL, | ||
865 | }; | ||
866 | |||
867 | static struct bin_attribute c2port_bin_attrs = { | ||
868 | .attr = { | ||
869 | .name = "flash_data", | ||
870 | .mode = 0644 | ||
871 | }, | ||
872 | .read = c2port_read_flash_data, | ||
873 | .write = c2port_write_flash_data, | ||
874 | /* .size is computed at run-time */ | ||
875 | }; | ||
876 | |||
877 | /* | ||
878 | * Exported functions | ||
879 | */ | ||
880 | |||
881 | struct c2port_device *c2port_device_register(char *name, | ||
882 | struct c2port_ops *ops, void *devdata) | ||
883 | { | ||
884 | struct c2port_device *c2dev; | ||
885 | int id, ret; | ||
886 | |||
887 | if (unlikely(!ops) || unlikely(!ops->access) || \ | ||
888 | unlikely(!ops->c2d_dir) || unlikely(!ops->c2ck_set) || \ | ||
889 | unlikely(!ops->c2d_get) || unlikely(!ops->c2d_set)) | ||
890 | return ERR_PTR(-EINVAL); | ||
891 | |||
892 | c2dev = kmalloc(sizeof(struct c2port_device), GFP_KERNEL); | ||
893 | if (unlikely(!c2dev)) | ||
894 | return ERR_PTR(-ENOMEM); | ||
895 | |||
896 | ret = idr_pre_get(&c2port_idr, GFP_KERNEL); | ||
897 | if (!ret) { | ||
898 | ret = -ENOMEM; | ||
899 | goto error_idr_get_new; | ||
900 | } | ||
901 | |||
902 | spin_lock_irq(&c2port_idr_lock); | ||
903 | ret = idr_get_new(&c2port_idr, c2dev, &id); | ||
904 | spin_unlock_irq(&c2port_idr_lock); | ||
905 | |||
906 | if (ret < 0) | ||
907 | goto error_idr_get_new; | ||
908 | c2dev->id = id; | ||
909 | |||
910 | c2dev->dev = device_create(c2port_class, NULL, 0, c2dev, | ||
911 | "c2port%d", id); | ||
912 | if (unlikely(!c2dev->dev)) { | ||
913 | ret = -ENOMEM; | ||
914 | goto error_device_create; | ||
915 | } | ||
916 | dev_set_drvdata(c2dev->dev, c2dev); | ||
917 | |||
918 | strncpy(c2dev->name, name, C2PORT_NAME_LEN); | ||
919 | c2dev->ops = ops; | ||
920 | mutex_init(&c2dev->mutex); | ||
921 | |||
922 | /* Create binary file */ | ||
923 | c2port_bin_attrs.size = ops->blocks_num * ops->block_size; | ||
924 | ret = device_create_bin_file(c2dev->dev, &c2port_bin_attrs); | ||
925 | if (unlikely(ret)) | ||
926 | goto error_device_create_bin_file; | ||
927 | |||
928 | /* By default C2 port access is off */ | ||
929 | c2dev->access = c2dev->flash_access = 0; | ||
930 | ops->access(c2dev, 0); | ||
931 | |||
932 | dev_info(c2dev->dev, "C2 port %s added\n", name); | ||
933 | dev_info(c2dev->dev, "%s flash has %d blocks x %d bytes " | ||
934 | "(%d bytes total)\n", | ||
935 | name, ops->blocks_num, ops->block_size, | ||
936 | ops->blocks_num * ops->block_size); | ||
937 | |||
938 | return c2dev; | ||
939 | |||
940 | error_device_create_bin_file: | ||
941 | device_destroy(c2port_class, 0); | ||
942 | |||
943 | error_device_create: | ||
944 | spin_lock_irq(&c2port_idr_lock); | ||
945 | idr_remove(&c2port_idr, id); | ||
946 | spin_unlock_irq(&c2port_idr_lock); | ||
947 | |||
948 | error_idr_get_new: | ||
949 | kfree(c2dev); | ||
950 | |||
951 | return ERR_PTR(ret); | ||
952 | } | ||
953 | EXPORT_SYMBOL(c2port_device_register); | ||
954 | |||
955 | void c2port_device_unregister(struct c2port_device *c2dev) | ||
956 | { | ||
957 | if (!c2dev) | ||
958 | return; | ||
959 | |||
960 | dev_info(c2dev->dev, "C2 port %s removed\n", c2dev->name); | ||
961 | |||
962 | device_remove_bin_file(c2dev->dev, &c2port_bin_attrs); | ||
963 | spin_lock_irq(&c2port_idr_lock); | ||
964 | idr_remove(&c2port_idr, c2dev->id); | ||
965 | spin_unlock_irq(&c2port_idr_lock); | ||
966 | |||
967 | device_destroy(c2port_class, c2dev->id); | ||
968 | |||
969 | kfree(c2dev); | ||
970 | } | ||
971 | EXPORT_SYMBOL(c2port_device_unregister); | ||
972 | |||
973 | /* | ||
974 | * Module stuff | ||
975 | */ | ||
976 | |||
977 | static int __init c2port_init(void) | ||
978 | { | ||
979 | printk(KERN_INFO "Silicon Labs C2 port support v. " DRIVER_VERSION | ||
980 | " - (C) 2007 Rodolfo Giometti\n"); | ||
981 | |||
982 | c2port_class = class_create(THIS_MODULE, "c2port"); | ||
983 | if (!c2port_class) { | ||
984 | printk(KERN_ERR "c2port: failed to allocate class\n"); | ||
985 | return -ENOMEM; | ||
986 | } | ||
987 | c2port_class->dev_attrs = c2port_attrs; | ||
988 | |||
989 | return 0; | ||
990 | } | ||
991 | |||
992 | static void __exit c2port_exit(void) | ||
993 | { | ||
994 | class_destroy(c2port_class); | ||
995 | } | ||
996 | |||
997 | module_init(c2port_init); | ||
998 | module_exit(c2port_exit); | ||
999 | |||
1000 | MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>"); | ||
1001 | MODULE_DESCRIPTION("Silicon Labs C2 port support v. " DRIVER_VERSION); | ||
1002 | MODULE_LICENSE("GPL"); | ||