diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/sbus/char/bbc_i2c.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/sbus/char/bbc_i2c.c')
-rw-r--r-- | drivers/sbus/char/bbc_i2c.c | 488 |
1 files changed, 488 insertions, 0 deletions
diff --git a/drivers/sbus/char/bbc_i2c.c b/drivers/sbus/char/bbc_i2c.c new file mode 100644 index 000000000000..1c8b612d8234 --- /dev/null +++ b/drivers/sbus/char/bbc_i2c.c | |||
@@ -0,0 +1,488 @@ | |||
1 | /* $Id: bbc_i2c.c,v 1.2 2001/04/02 09:59:08 davem Exp $ | ||
2 | * bbc_i2c.c: I2C low-level driver for BBC device on UltraSPARC-III | ||
3 | * platforms. | ||
4 | * | ||
5 | * Copyright (C) 2001 David S. Miller (davem@redhat.com) | ||
6 | */ | ||
7 | |||
8 | #include <linux/module.h> | ||
9 | #include <linux/kernel.h> | ||
10 | #include <linux/types.h> | ||
11 | #include <linux/slab.h> | ||
12 | #include <linux/sched.h> | ||
13 | #include <linux/wait.h> | ||
14 | #include <linux/delay.h> | ||
15 | #include <linux/init.h> | ||
16 | #include <linux/interrupt.h> | ||
17 | #include <asm/oplib.h> | ||
18 | #include <asm/ebus.h> | ||
19 | #include <asm/spitfire.h> | ||
20 | #include <asm/bbc.h> | ||
21 | |||
22 | #include "bbc_i2c.h" | ||
23 | |||
24 | /* Convert this driver to use i2c bus layer someday... */ | ||
25 | #define I2C_PCF_PIN 0x80 | ||
26 | #define I2C_PCF_ESO 0x40 | ||
27 | #define I2C_PCF_ES1 0x20 | ||
28 | #define I2C_PCF_ES2 0x10 | ||
29 | #define I2C_PCF_ENI 0x08 | ||
30 | #define I2C_PCF_STA 0x04 | ||
31 | #define I2C_PCF_STO 0x02 | ||
32 | #define I2C_PCF_ACK 0x01 | ||
33 | |||
34 | #define I2C_PCF_START (I2C_PCF_PIN | I2C_PCF_ESO | I2C_PCF_ENI | I2C_PCF_STA | I2C_PCF_ACK) | ||
35 | #define I2C_PCF_STOP (I2C_PCF_PIN | I2C_PCF_ESO | I2C_PCF_STO | I2C_PCF_ACK) | ||
36 | #define I2C_PCF_REPSTART ( I2C_PCF_ESO | I2C_PCF_STA | I2C_PCF_ACK) | ||
37 | #define I2C_PCF_IDLE (I2C_PCF_PIN | I2C_PCF_ESO | I2C_PCF_ACK) | ||
38 | |||
39 | #define I2C_PCF_INI 0x40 /* 1 if not initialized */ | ||
40 | #define I2C_PCF_STS 0x20 | ||
41 | #define I2C_PCF_BER 0x10 | ||
42 | #define I2C_PCF_AD0 0x08 | ||
43 | #define I2C_PCF_LRB 0x08 | ||
44 | #define I2C_PCF_AAS 0x04 | ||
45 | #define I2C_PCF_LAB 0x02 | ||
46 | #define I2C_PCF_BB 0x01 | ||
47 | |||
48 | /* The BBC devices have two I2C controllers. The first I2C controller | ||
49 | * connects mainly to configuration proms (NVRAM, cpu configuration, | ||
50 | * dimm types, etc.). Whereas the second I2C controller connects to | ||
51 | * environmental control devices such as fans and temperature sensors. | ||
52 | * The second controller also connects to the smartcard reader, if present. | ||
53 | */ | ||
54 | |||
55 | #define NUM_CHILDREN 8 | ||
56 | struct bbc_i2c_bus { | ||
57 | struct bbc_i2c_bus *next; | ||
58 | int index; | ||
59 | spinlock_t lock; | ||
60 | void __iomem *i2c_bussel_reg; | ||
61 | void __iomem *i2c_control_regs; | ||
62 | unsigned char own, clock; | ||
63 | |||
64 | wait_queue_head_t wq; | ||
65 | volatile int waiting; | ||
66 | |||
67 | struct linux_ebus_device *bus_edev; | ||
68 | struct { | ||
69 | struct linux_ebus_child *device; | ||
70 | int client_claimed; | ||
71 | } devs[NUM_CHILDREN]; | ||
72 | }; | ||
73 | |||
74 | static struct bbc_i2c_bus *all_bbc_i2c; | ||
75 | |||
76 | struct bbc_i2c_client { | ||
77 | struct bbc_i2c_bus *bp; | ||
78 | struct linux_ebus_child *echild; | ||
79 | int bus; | ||
80 | int address; | ||
81 | }; | ||
82 | |||
83 | static int find_device(struct bbc_i2c_bus *bp, struct linux_ebus_child *echild) | ||
84 | { | ||
85 | int i; | ||
86 | |||
87 | for (i = 0; i < NUM_CHILDREN; i++) { | ||
88 | if (bp->devs[i].device == echild) { | ||
89 | if (bp->devs[i].client_claimed) | ||
90 | return 0; | ||
91 | return 1; | ||
92 | } | ||
93 | } | ||
94 | return 0; | ||
95 | } | ||
96 | |||
97 | static void set_device_claimage(struct bbc_i2c_bus *bp, struct linux_ebus_child *echild, int val) | ||
98 | { | ||
99 | int i; | ||
100 | |||
101 | for (i = 0; i < NUM_CHILDREN; i++) { | ||
102 | if (bp->devs[i].device == echild) { | ||
103 | bp->devs[i].client_claimed = val; | ||
104 | return; | ||
105 | } | ||
106 | } | ||
107 | } | ||
108 | |||
109 | #define claim_device(BP,ECHILD) set_device_claimage(BP,ECHILD,1) | ||
110 | #define release_device(BP,ECHILD) set_device_claimage(BP,ECHILD,0) | ||
111 | |||
112 | static struct bbc_i2c_bus *find_bus_for_device(struct linux_ebus_child *echild) | ||
113 | { | ||
114 | struct bbc_i2c_bus *bp = all_bbc_i2c; | ||
115 | |||
116 | while (bp != NULL) { | ||
117 | if (find_device(bp, echild) != 0) | ||
118 | break; | ||
119 | bp = bp->next; | ||
120 | } | ||
121 | |||
122 | return bp; | ||
123 | } | ||
124 | |||
125 | struct linux_ebus_child *bbc_i2c_getdev(int index) | ||
126 | { | ||
127 | struct bbc_i2c_bus *bp = all_bbc_i2c; | ||
128 | struct linux_ebus_child *echild = NULL; | ||
129 | int curidx = 0; | ||
130 | |||
131 | while (bp != NULL) { | ||
132 | struct bbc_i2c_bus *next = bp->next; | ||
133 | int i; | ||
134 | |||
135 | for (i = 0; i < NUM_CHILDREN; i++) { | ||
136 | if (!(echild = bp->devs[i].device)) | ||
137 | break; | ||
138 | if (curidx == index) | ||
139 | goto out; | ||
140 | echild = NULL; | ||
141 | curidx++; | ||
142 | } | ||
143 | bp = next; | ||
144 | } | ||
145 | out: | ||
146 | if (curidx == index) | ||
147 | return echild; | ||
148 | return NULL; | ||
149 | } | ||
150 | |||
151 | struct bbc_i2c_client *bbc_i2c_attach(struct linux_ebus_child *echild) | ||
152 | { | ||
153 | struct bbc_i2c_bus *bp = find_bus_for_device(echild); | ||
154 | struct bbc_i2c_client *client; | ||
155 | |||
156 | if (!bp) | ||
157 | return NULL; | ||
158 | client = kmalloc(sizeof(*client), GFP_KERNEL); | ||
159 | if (!client) | ||
160 | return NULL; | ||
161 | memset(client, 0, sizeof(*client)); | ||
162 | client->bp = bp; | ||
163 | client->echild = echild; | ||
164 | client->bus = echild->resource[0].start; | ||
165 | client->address = echild->resource[1].start; | ||
166 | |||
167 | claim_device(bp, echild); | ||
168 | |||
169 | return client; | ||
170 | } | ||
171 | |||
172 | void bbc_i2c_detach(struct bbc_i2c_client *client) | ||
173 | { | ||
174 | struct bbc_i2c_bus *bp = client->bp; | ||
175 | struct linux_ebus_child *echild = client->echild; | ||
176 | |||
177 | release_device(bp, echild); | ||
178 | kfree(client); | ||
179 | } | ||
180 | |||
181 | static int wait_for_pin(struct bbc_i2c_bus *bp, u8 *status) | ||
182 | { | ||
183 | DECLARE_WAITQUEUE(wait, current); | ||
184 | int limit = 32; | ||
185 | int ret = 1; | ||
186 | |||
187 | bp->waiting = 1; | ||
188 | add_wait_queue(&bp->wq, &wait); | ||
189 | while (limit-- > 0) { | ||
190 | u8 val; | ||
191 | |||
192 | set_current_state(TASK_INTERRUPTIBLE); | ||
193 | *status = val = readb(bp->i2c_control_regs + 0); | ||
194 | if ((val & I2C_PCF_PIN) == 0) { | ||
195 | ret = 0; | ||
196 | break; | ||
197 | } | ||
198 | msleep_interruptible(250); | ||
199 | } | ||
200 | remove_wait_queue(&bp->wq, &wait); | ||
201 | bp->waiting = 0; | ||
202 | current->state = TASK_RUNNING; | ||
203 | |||
204 | return ret; | ||
205 | } | ||
206 | |||
207 | int bbc_i2c_writeb(struct bbc_i2c_client *client, unsigned char val, int off) | ||
208 | { | ||
209 | struct bbc_i2c_bus *bp = client->bp; | ||
210 | int address = client->address; | ||
211 | u8 status; | ||
212 | int ret = -1; | ||
213 | |||
214 | if (bp->i2c_bussel_reg != NULL) | ||
215 | writeb(client->bus, bp->i2c_bussel_reg); | ||
216 | |||
217 | writeb(address, bp->i2c_control_regs + 0x1); | ||
218 | writeb(I2C_PCF_START, bp->i2c_control_regs + 0x0); | ||
219 | if (wait_for_pin(bp, &status)) | ||
220 | goto out; | ||
221 | |||
222 | writeb(off, bp->i2c_control_regs + 0x1); | ||
223 | if (wait_for_pin(bp, &status) || | ||
224 | (status & I2C_PCF_LRB) != 0) | ||
225 | goto out; | ||
226 | |||
227 | writeb(val, bp->i2c_control_regs + 0x1); | ||
228 | if (wait_for_pin(bp, &status)) | ||
229 | goto out; | ||
230 | |||
231 | ret = 0; | ||
232 | |||
233 | out: | ||
234 | writeb(I2C_PCF_STOP, bp->i2c_control_regs + 0x0); | ||
235 | return ret; | ||
236 | } | ||
237 | |||
238 | int bbc_i2c_readb(struct bbc_i2c_client *client, unsigned char *byte, int off) | ||
239 | { | ||
240 | struct bbc_i2c_bus *bp = client->bp; | ||
241 | unsigned char address = client->address, status; | ||
242 | int ret = -1; | ||
243 | |||
244 | if (bp->i2c_bussel_reg != NULL) | ||
245 | writeb(client->bus, bp->i2c_bussel_reg); | ||
246 | |||
247 | writeb(address, bp->i2c_control_regs + 0x1); | ||
248 | writeb(I2C_PCF_START, bp->i2c_control_regs + 0x0); | ||
249 | if (wait_for_pin(bp, &status)) | ||
250 | goto out; | ||
251 | |||
252 | writeb(off, bp->i2c_control_regs + 0x1); | ||
253 | if (wait_for_pin(bp, &status) || | ||
254 | (status & I2C_PCF_LRB) != 0) | ||
255 | goto out; | ||
256 | |||
257 | writeb(I2C_PCF_STOP, bp->i2c_control_regs + 0x0); | ||
258 | |||
259 | address |= 0x1; /* READ */ | ||
260 | |||
261 | writeb(address, bp->i2c_control_regs + 0x1); | ||
262 | writeb(I2C_PCF_START, bp->i2c_control_regs + 0x0); | ||
263 | if (wait_for_pin(bp, &status)) | ||
264 | goto out; | ||
265 | |||
266 | /* Set PIN back to one so the device sends the first | ||
267 | * byte. | ||
268 | */ | ||
269 | (void) readb(bp->i2c_control_regs + 0x1); | ||
270 | if (wait_for_pin(bp, &status)) | ||
271 | goto out; | ||
272 | |||
273 | writeb(I2C_PCF_ESO | I2C_PCF_ENI, bp->i2c_control_regs + 0x0); | ||
274 | *byte = readb(bp->i2c_control_regs + 0x1); | ||
275 | if (wait_for_pin(bp, &status)) | ||
276 | goto out; | ||
277 | |||
278 | ret = 0; | ||
279 | |||
280 | out: | ||
281 | writeb(I2C_PCF_STOP, bp->i2c_control_regs + 0x0); | ||
282 | (void) readb(bp->i2c_control_regs + 0x1); | ||
283 | |||
284 | return ret; | ||
285 | } | ||
286 | |||
287 | int bbc_i2c_write_buf(struct bbc_i2c_client *client, | ||
288 | char *buf, int len, int off) | ||
289 | { | ||
290 | int ret = 0; | ||
291 | |||
292 | while (len > 0) { | ||
293 | int err = bbc_i2c_writeb(client, *buf, off); | ||
294 | |||
295 | if (err < 0) { | ||
296 | ret = err; | ||
297 | break; | ||
298 | } | ||
299 | |||
300 | len--; | ||
301 | buf++; | ||
302 | off++; | ||
303 | } | ||
304 | return ret; | ||
305 | } | ||
306 | |||
307 | int bbc_i2c_read_buf(struct bbc_i2c_client *client, | ||
308 | char *buf, int len, int off) | ||
309 | { | ||
310 | int ret = 0; | ||
311 | |||
312 | while (len > 0) { | ||
313 | int err = bbc_i2c_readb(client, buf, off); | ||
314 | if (err < 0) { | ||
315 | ret = err; | ||
316 | break; | ||
317 | } | ||
318 | len--; | ||
319 | buf++; | ||
320 | off++; | ||
321 | } | ||
322 | |||
323 | return ret; | ||
324 | } | ||
325 | |||
326 | EXPORT_SYMBOL(bbc_i2c_getdev); | ||
327 | EXPORT_SYMBOL(bbc_i2c_attach); | ||
328 | EXPORT_SYMBOL(bbc_i2c_detach); | ||
329 | EXPORT_SYMBOL(bbc_i2c_writeb); | ||
330 | EXPORT_SYMBOL(bbc_i2c_readb); | ||
331 | EXPORT_SYMBOL(bbc_i2c_write_buf); | ||
332 | EXPORT_SYMBOL(bbc_i2c_read_buf); | ||
333 | |||
334 | static irqreturn_t bbc_i2c_interrupt(int irq, void *dev_id, struct pt_regs *regs) | ||
335 | { | ||
336 | struct bbc_i2c_bus *bp = dev_id; | ||
337 | |||
338 | /* PIN going from set to clear is the only event which | ||
339 | * makes the i2c assert an interrupt. | ||
340 | */ | ||
341 | if (bp->waiting && | ||
342 | !(readb(bp->i2c_control_regs + 0x0) & I2C_PCF_PIN)) | ||
343 | wake_up(&bp->wq); | ||
344 | |||
345 | return IRQ_HANDLED; | ||
346 | } | ||
347 | |||
348 | static void __init reset_one_i2c(struct bbc_i2c_bus *bp) | ||
349 | { | ||
350 | writeb(I2C_PCF_PIN, bp->i2c_control_regs + 0x0); | ||
351 | writeb(bp->own, bp->i2c_control_regs + 0x1); | ||
352 | writeb(I2C_PCF_PIN | I2C_PCF_ES1, bp->i2c_control_regs + 0x0); | ||
353 | writeb(bp->clock, bp->i2c_control_regs + 0x1); | ||
354 | writeb(I2C_PCF_IDLE, bp->i2c_control_regs + 0x0); | ||
355 | } | ||
356 | |||
357 | static int __init attach_one_i2c(struct linux_ebus_device *edev, int index) | ||
358 | { | ||
359 | struct bbc_i2c_bus *bp = kmalloc(sizeof(*bp), GFP_KERNEL); | ||
360 | struct linux_ebus_child *echild; | ||
361 | int entry; | ||
362 | |||
363 | if (!bp) | ||
364 | return -ENOMEM; | ||
365 | memset(bp, 0, sizeof(*bp)); | ||
366 | |||
367 | bp->i2c_control_regs = ioremap(edev->resource[0].start, 0x2); | ||
368 | if (!bp->i2c_control_regs) | ||
369 | goto fail; | ||
370 | |||
371 | if (edev->num_addrs == 2) { | ||
372 | bp->i2c_bussel_reg = ioremap(edev->resource[1].start, 0x1); | ||
373 | if (!bp->i2c_bussel_reg) | ||
374 | goto fail; | ||
375 | } | ||
376 | |||
377 | bp->waiting = 0; | ||
378 | init_waitqueue_head(&bp->wq); | ||
379 | if (request_irq(edev->irqs[0], bbc_i2c_interrupt, | ||
380 | SA_SHIRQ, "bbc_i2c", bp)) | ||
381 | goto fail; | ||
382 | |||
383 | bp->index = index; | ||
384 | bp->bus_edev = edev; | ||
385 | |||
386 | spin_lock_init(&bp->lock); | ||
387 | bp->next = all_bbc_i2c; | ||
388 | all_bbc_i2c = bp; | ||
389 | |||
390 | entry = 0; | ||
391 | for (echild = edev->children; | ||
392 | echild && entry < 8; | ||
393 | echild = echild->next, entry++) { | ||
394 | bp->devs[entry].device = echild; | ||
395 | bp->devs[entry].client_claimed = 0; | ||
396 | } | ||
397 | |||
398 | writeb(I2C_PCF_PIN, bp->i2c_control_regs + 0x0); | ||
399 | bp->own = readb(bp->i2c_control_regs + 0x01); | ||
400 | writeb(I2C_PCF_PIN | I2C_PCF_ES1, bp->i2c_control_regs + 0x0); | ||
401 | bp->clock = readb(bp->i2c_control_regs + 0x01); | ||
402 | |||
403 | printk(KERN_INFO "i2c-%d: Regs at %p, %d devices, own %02x, clock %02x.\n", | ||
404 | bp->index, bp->i2c_control_regs, entry, bp->own, bp->clock); | ||
405 | |||
406 | reset_one_i2c(bp); | ||
407 | |||
408 | return 0; | ||
409 | |||
410 | fail: | ||
411 | if (bp->i2c_bussel_reg) | ||
412 | iounmap(bp->i2c_bussel_reg); | ||
413 | if (bp->i2c_control_regs) | ||
414 | iounmap(bp->i2c_control_regs); | ||
415 | kfree(bp); | ||
416 | return -EINVAL; | ||
417 | } | ||
418 | |||
419 | static int __init bbc_present(void) | ||
420 | { | ||
421 | struct linux_ebus *ebus = NULL; | ||
422 | struct linux_ebus_device *edev = NULL; | ||
423 | |||
424 | for_each_ebus(ebus) { | ||
425 | for_each_ebusdev(edev, ebus) { | ||
426 | if (!strcmp(edev->prom_name, "bbc")) | ||
427 | return 1; | ||
428 | } | ||
429 | } | ||
430 | return 0; | ||
431 | } | ||
432 | |||
433 | extern int bbc_envctrl_init(void); | ||
434 | extern void bbc_envctrl_cleanup(void); | ||
435 | static void bbc_i2c_cleanup(void); | ||
436 | |||
437 | static int __init bbc_i2c_init(void) | ||
438 | { | ||
439 | struct linux_ebus *ebus = NULL; | ||
440 | struct linux_ebus_device *edev = NULL; | ||
441 | int err, index = 0; | ||
442 | |||
443 | if (tlb_type != cheetah || !bbc_present()) | ||
444 | return -ENODEV; | ||
445 | |||
446 | for_each_ebus(ebus) { | ||
447 | for_each_ebusdev(edev, ebus) { | ||
448 | if (!strcmp(edev->prom_name, "i2c")) { | ||
449 | if (!attach_one_i2c(edev, index)) | ||
450 | index++; | ||
451 | } | ||
452 | } | ||
453 | } | ||
454 | |||
455 | if (!index) | ||
456 | return -ENODEV; | ||
457 | |||
458 | err = bbc_envctrl_init(); | ||
459 | if (err) | ||
460 | bbc_i2c_cleanup(); | ||
461 | return err; | ||
462 | } | ||
463 | |||
464 | static void bbc_i2c_cleanup(void) | ||
465 | { | ||
466 | struct bbc_i2c_bus *bp = all_bbc_i2c; | ||
467 | |||
468 | bbc_envctrl_cleanup(); | ||
469 | |||
470 | while (bp != NULL) { | ||
471 | struct bbc_i2c_bus *next = bp->next; | ||
472 | |||
473 | free_irq(bp->bus_edev->irqs[0], bp); | ||
474 | |||
475 | if (bp->i2c_bussel_reg) | ||
476 | iounmap(bp->i2c_bussel_reg); | ||
477 | if (bp->i2c_control_regs) | ||
478 | iounmap(bp->i2c_control_regs); | ||
479 | |||
480 | kfree(bp); | ||
481 | |||
482 | bp = next; | ||
483 | } | ||
484 | all_bbc_i2c = NULL; | ||
485 | } | ||
486 | |||
487 | module_init(bbc_i2c_init); | ||
488 | module_exit(bbc_i2c_cleanup); | ||