diff options
Diffstat (limited to 'arch/ppc64/kernel/pmac_low_i2c.c')
-rw-r--r-- | arch/ppc64/kernel/pmac_low_i2c.c | 523 |
1 files changed, 523 insertions, 0 deletions
diff --git a/arch/ppc64/kernel/pmac_low_i2c.c b/arch/ppc64/kernel/pmac_low_i2c.c new file mode 100644 index 000000000000..f3f39e8e337a --- /dev/null +++ b/arch/ppc64/kernel/pmac_low_i2c.c | |||
@@ -0,0 +1,523 @@ | |||
1 | /* | ||
2 | * arch/ppc/platforms/pmac_low_i2c.c | ||
3 | * | ||
4 | * Copyright (C) 2003 Ben. Herrenschmidt (benh@kernel.crashing.org) | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or | ||
7 | * modify it under the terms of the GNU General Public License | ||
8 | * as published by the Free Software Foundation; either version | ||
9 | * 2 of the License, or (at your option) any later version. | ||
10 | * | ||
11 | * This file contains some low-level i2c access routines that | ||
12 | * need to be used by various bits of the PowerMac platform code | ||
13 | * at times where the real asynchronous & interrupt driven driver | ||
14 | * cannot be used. The API borrows some semantics from the darwin | ||
15 | * driver in order to ease the implementation of the platform | ||
16 | * properties parser | ||
17 | */ | ||
18 | |||
19 | #undef DEBUG | ||
20 | |||
21 | #include <linux/config.h> | ||
22 | #include <linux/types.h> | ||
23 | #include <linux/sched.h> | ||
24 | #include <linux/init.h> | ||
25 | #include <linux/module.h> | ||
26 | #include <linux/adb.h> | ||
27 | #include <linux/pmu.h> | ||
28 | #include <asm/keylargo.h> | ||
29 | #include <asm/uninorth.h> | ||
30 | #include <asm/io.h> | ||
31 | #include <asm/prom.h> | ||
32 | #include <asm/machdep.h> | ||
33 | #include <asm/pmac_low_i2c.h> | ||
34 | |||
35 | #define MAX_LOW_I2C_HOST 4 | ||
36 | |||
37 | #ifdef DEBUG | ||
38 | #define DBG(x...) do {\ | ||
39 | printk(KERN_DEBUG "KW:" x); \ | ||
40 | } while(0) | ||
41 | #else | ||
42 | #define DBG(x...) | ||
43 | #endif | ||
44 | |||
45 | struct low_i2c_host; | ||
46 | |||
47 | typedef int (*low_i2c_func_t)(struct low_i2c_host *host, u8 addr, u8 sub, u8 *data, int len); | ||
48 | |||
49 | struct low_i2c_host | ||
50 | { | ||
51 | struct device_node *np; /* OF device node */ | ||
52 | struct semaphore mutex; /* Access mutex for use by i2c-keywest */ | ||
53 | low_i2c_func_t func; /* Access function */ | ||
54 | unsigned int is_open : 1; /* Poor man's access control */ | ||
55 | int mode; /* Current mode */ | ||
56 | int channel; /* Current channel */ | ||
57 | int num_channels; /* Number of channels */ | ||
58 | void __iomem *base; /* For keywest-i2c, base address */ | ||
59 | int bsteps; /* And register stepping */ | ||
60 | int speed; /* And speed */ | ||
61 | }; | ||
62 | |||
63 | static struct low_i2c_host low_i2c_hosts[MAX_LOW_I2C_HOST]; | ||
64 | |||
65 | /* No locking is necessary on allocation, we are running way before | ||
66 | * anything can race with us | ||
67 | */ | ||
68 | static struct low_i2c_host *find_low_i2c_host(struct device_node *np) | ||
69 | { | ||
70 | int i; | ||
71 | |||
72 | for (i = 0; i < MAX_LOW_I2C_HOST; i++) | ||
73 | if (low_i2c_hosts[i].np == np) | ||
74 | return &low_i2c_hosts[i]; | ||
75 | return NULL; | ||
76 | } | ||
77 | |||
78 | /* | ||
79 | * | ||
80 | * i2c-keywest implementation (UniNorth, U2, U3, Keylargo's) | ||
81 | * | ||
82 | */ | ||
83 | |||
84 | /* | ||
85 | * Keywest i2c definitions borrowed from drivers/i2c/i2c-keywest.h, | ||
86 | * should be moved somewhere in include/asm-ppc/ | ||
87 | */ | ||
88 | /* Register indices */ | ||
89 | typedef enum { | ||
90 | reg_mode = 0, | ||
91 | reg_control, | ||
92 | reg_status, | ||
93 | reg_isr, | ||
94 | reg_ier, | ||
95 | reg_addr, | ||
96 | reg_subaddr, | ||
97 | reg_data | ||
98 | } reg_t; | ||
99 | |||
100 | |||
101 | /* Mode register */ | ||
102 | #define KW_I2C_MODE_100KHZ 0x00 | ||
103 | #define KW_I2C_MODE_50KHZ 0x01 | ||
104 | #define KW_I2C_MODE_25KHZ 0x02 | ||
105 | #define KW_I2C_MODE_DUMB 0x00 | ||
106 | #define KW_I2C_MODE_STANDARD 0x04 | ||
107 | #define KW_I2C_MODE_STANDARDSUB 0x08 | ||
108 | #define KW_I2C_MODE_COMBINED 0x0C | ||
109 | #define KW_I2C_MODE_MODE_MASK 0x0C | ||
110 | #define KW_I2C_MODE_CHAN_MASK 0xF0 | ||
111 | |||
112 | /* Control register */ | ||
113 | #define KW_I2C_CTL_AAK 0x01 | ||
114 | #define KW_I2C_CTL_XADDR 0x02 | ||
115 | #define KW_I2C_CTL_STOP 0x04 | ||
116 | #define KW_I2C_CTL_START 0x08 | ||
117 | |||
118 | /* Status register */ | ||
119 | #define KW_I2C_STAT_BUSY 0x01 | ||
120 | #define KW_I2C_STAT_LAST_AAK 0x02 | ||
121 | #define KW_I2C_STAT_LAST_RW 0x04 | ||
122 | #define KW_I2C_STAT_SDA 0x08 | ||
123 | #define KW_I2C_STAT_SCL 0x10 | ||
124 | |||
125 | /* IER & ISR registers */ | ||
126 | #define KW_I2C_IRQ_DATA 0x01 | ||
127 | #define KW_I2C_IRQ_ADDR 0x02 | ||
128 | #define KW_I2C_IRQ_STOP 0x04 | ||
129 | #define KW_I2C_IRQ_START 0x08 | ||
130 | #define KW_I2C_IRQ_MASK 0x0F | ||
131 | |||
132 | /* State machine states */ | ||
133 | enum { | ||
134 | state_idle, | ||
135 | state_addr, | ||
136 | state_read, | ||
137 | state_write, | ||
138 | state_stop, | ||
139 | state_dead | ||
140 | }; | ||
141 | |||
142 | #define WRONG_STATE(name) do {\ | ||
143 | printk(KERN_DEBUG "KW: wrong state. Got %s, state: %s (isr: %02x)\n", \ | ||
144 | name, __kw_state_names[state], isr); \ | ||
145 | } while(0) | ||
146 | |||
147 | static const char *__kw_state_names[] = { | ||
148 | "state_idle", | ||
149 | "state_addr", | ||
150 | "state_read", | ||
151 | "state_write", | ||
152 | "state_stop", | ||
153 | "state_dead" | ||
154 | }; | ||
155 | |||
156 | static inline u8 __kw_read_reg(struct low_i2c_host *host, reg_t reg) | ||
157 | { | ||
158 | return readb(host->base + (((unsigned int)reg) << host->bsteps)); | ||
159 | } | ||
160 | |||
161 | static inline void __kw_write_reg(struct low_i2c_host *host, reg_t reg, u8 val) | ||
162 | { | ||
163 | writeb(val, host->base + (((unsigned)reg) << host->bsteps)); | ||
164 | (void)__kw_read_reg(host, reg_subaddr); | ||
165 | } | ||
166 | |||
167 | #define kw_write_reg(reg, val) __kw_write_reg(host, reg, val) | ||
168 | #define kw_read_reg(reg) __kw_read_reg(host, reg) | ||
169 | |||
170 | |||
171 | /* Don't schedule, the g5 fan controller is too | ||
172 | * timing sensitive | ||
173 | */ | ||
174 | static u8 kw_wait_interrupt(struct low_i2c_host* host) | ||
175 | { | ||
176 | int i, j; | ||
177 | u8 isr; | ||
178 | |||
179 | for (i = 0; i < 100000; i++) { | ||
180 | isr = kw_read_reg(reg_isr) & KW_I2C_IRQ_MASK; | ||
181 | if (isr != 0) | ||
182 | return isr; | ||
183 | |||
184 | /* This code is used with the timebase frozen, we cannot rely | ||
185 | * on udelay ! For now, just use a bogus loop | ||
186 | */ | ||
187 | for (j = 1; j < 10000; j++) | ||
188 | mb(); | ||
189 | } | ||
190 | return isr; | ||
191 | } | ||
192 | |||
193 | static int kw_handle_interrupt(struct low_i2c_host *host, int state, int rw, int *rc, u8 **data, int *len, u8 isr) | ||
194 | { | ||
195 | u8 ack; | ||
196 | |||
197 | DBG("kw_handle_interrupt(%s, isr: %x)\n", __kw_state_names[state], isr); | ||
198 | |||
199 | if (isr == 0) { | ||
200 | if (state != state_stop) { | ||
201 | DBG("KW: Timeout !\n"); | ||
202 | *rc = -EIO; | ||
203 | goto stop; | ||
204 | } | ||
205 | if (state == state_stop) { | ||
206 | ack = kw_read_reg(reg_status); | ||
207 | if (!(ack & KW_I2C_STAT_BUSY)) { | ||
208 | state = state_idle; | ||
209 | kw_write_reg(reg_ier, 0x00); | ||
210 | } | ||
211 | } | ||
212 | return state; | ||
213 | } | ||
214 | |||
215 | if (isr & KW_I2C_IRQ_ADDR) { | ||
216 | ack = kw_read_reg(reg_status); | ||
217 | if (state != state_addr) { | ||
218 | kw_write_reg(reg_isr, KW_I2C_IRQ_ADDR); | ||
219 | WRONG_STATE("KW_I2C_IRQ_ADDR"); | ||
220 | *rc = -EIO; | ||
221 | goto stop; | ||
222 | } | ||
223 | if ((ack & KW_I2C_STAT_LAST_AAK) == 0) { | ||
224 | *rc = -ENODEV; | ||
225 | DBG("KW: NAK on address\n"); | ||
226 | return state_stop; | ||
227 | } else { | ||
228 | if (rw) { | ||
229 | state = state_read; | ||
230 | if (*len > 1) | ||
231 | kw_write_reg(reg_control, KW_I2C_CTL_AAK); | ||
232 | } else { | ||
233 | state = state_write; | ||
234 | kw_write_reg(reg_data, **data); | ||
235 | (*data)++; (*len)--; | ||
236 | } | ||
237 | } | ||
238 | kw_write_reg(reg_isr, KW_I2C_IRQ_ADDR); | ||
239 | } | ||
240 | |||
241 | if (isr & KW_I2C_IRQ_DATA) { | ||
242 | if (state == state_read) { | ||
243 | **data = kw_read_reg(reg_data); | ||
244 | (*data)++; (*len)--; | ||
245 | kw_write_reg(reg_isr, KW_I2C_IRQ_DATA); | ||
246 | if ((*len) == 0) | ||
247 | state = state_stop; | ||
248 | else if ((*len) == 1) | ||
249 | kw_write_reg(reg_control, 0); | ||
250 | } else if (state == state_write) { | ||
251 | ack = kw_read_reg(reg_status); | ||
252 | if ((ack & KW_I2C_STAT_LAST_AAK) == 0) { | ||
253 | DBG("KW: nack on data write\n"); | ||
254 | *rc = -EIO; | ||
255 | goto stop; | ||
256 | } else if (*len) { | ||
257 | kw_write_reg(reg_data, **data); | ||
258 | (*data)++; (*len)--; | ||
259 | } else { | ||
260 | kw_write_reg(reg_control, KW_I2C_CTL_STOP); | ||
261 | state = state_stop; | ||
262 | *rc = 0; | ||
263 | } | ||
264 | kw_write_reg(reg_isr, KW_I2C_IRQ_DATA); | ||
265 | } else { | ||
266 | kw_write_reg(reg_isr, KW_I2C_IRQ_DATA); | ||
267 | WRONG_STATE("KW_I2C_IRQ_DATA"); | ||
268 | if (state != state_stop) { | ||
269 | *rc = -EIO; | ||
270 | goto stop; | ||
271 | } | ||
272 | } | ||
273 | } | ||
274 | |||
275 | if (isr & KW_I2C_IRQ_STOP) { | ||
276 | kw_write_reg(reg_isr, KW_I2C_IRQ_STOP); | ||
277 | if (state != state_stop) { | ||
278 | WRONG_STATE("KW_I2C_IRQ_STOP"); | ||
279 | *rc = -EIO; | ||
280 | } | ||
281 | return state_idle; | ||
282 | } | ||
283 | |||
284 | if (isr & KW_I2C_IRQ_START) | ||
285 | kw_write_reg(reg_isr, KW_I2C_IRQ_START); | ||
286 | |||
287 | return state; | ||
288 | |||
289 | stop: | ||
290 | kw_write_reg(reg_control, KW_I2C_CTL_STOP); | ||
291 | return state_stop; | ||
292 | } | ||
293 | |||
294 | static int keywest_low_i2c_func(struct low_i2c_host *host, u8 addr, u8 subaddr, u8 *data, int len) | ||
295 | { | ||
296 | u8 mode_reg = host->speed; | ||
297 | int state = state_addr; | ||
298 | int rc = 0; | ||
299 | |||
300 | /* Setup mode & subaddress if any */ | ||
301 | switch(host->mode) { | ||
302 | case pmac_low_i2c_mode_dumb: | ||
303 | printk(KERN_ERR "low_i2c: Dumb mode not supported !\n"); | ||
304 | return -EINVAL; | ||
305 | case pmac_low_i2c_mode_std: | ||
306 | mode_reg |= KW_I2C_MODE_STANDARD; | ||
307 | break; | ||
308 | case pmac_low_i2c_mode_stdsub: | ||
309 | mode_reg |= KW_I2C_MODE_STANDARDSUB; | ||
310 | break; | ||
311 | case pmac_low_i2c_mode_combined: | ||
312 | mode_reg |= KW_I2C_MODE_COMBINED; | ||
313 | break; | ||
314 | } | ||
315 | |||
316 | /* Setup channel & clear pending irqs */ | ||
317 | kw_write_reg(reg_isr, kw_read_reg(reg_isr)); | ||
318 | kw_write_reg(reg_mode, mode_reg | (host->channel << 4)); | ||
319 | kw_write_reg(reg_status, 0); | ||
320 | |||
321 | /* Set up address and r/w bit */ | ||
322 | kw_write_reg(reg_addr, addr); | ||
323 | |||
324 | /* Set up the sub address */ | ||
325 | if ((mode_reg & KW_I2C_MODE_MODE_MASK) == KW_I2C_MODE_STANDARDSUB | ||
326 | || (mode_reg & KW_I2C_MODE_MODE_MASK) == KW_I2C_MODE_COMBINED) | ||
327 | kw_write_reg(reg_subaddr, subaddr); | ||
328 | |||
329 | /* Start sending address & disable interrupt*/ | ||
330 | kw_write_reg(reg_ier, 0 /*KW_I2C_IRQ_MASK*/); | ||
331 | kw_write_reg(reg_control, KW_I2C_CTL_XADDR); | ||
332 | |||
333 | /* State machine, to turn into an interrupt handler */ | ||
334 | while(state != state_idle) { | ||
335 | u8 isr = kw_wait_interrupt(host); | ||
336 | state = kw_handle_interrupt(host, state, addr & 1, &rc, &data, &len, isr); | ||
337 | } | ||
338 | |||
339 | return rc; | ||
340 | } | ||
341 | |||
342 | static void keywest_low_i2c_add(struct device_node *np) | ||
343 | { | ||
344 | struct low_i2c_host *host = find_low_i2c_host(NULL); | ||
345 | u32 *psteps, *prate, steps, aoffset = 0; | ||
346 | struct device_node *parent; | ||
347 | |||
348 | if (host == NULL) { | ||
349 | printk(KERN_ERR "low_i2c: Can't allocate host for %s\n", | ||
350 | np->full_name); | ||
351 | return; | ||
352 | } | ||
353 | memset(host, 0, sizeof(*host)); | ||
354 | |||
355 | init_MUTEX(&host->mutex); | ||
356 | host->np = of_node_get(np); | ||
357 | psteps = (u32 *)get_property(np, "AAPL,address-step", NULL); | ||
358 | steps = psteps ? (*psteps) : 0x10; | ||
359 | for (host->bsteps = 0; (steps & 0x01) == 0; host->bsteps++) | ||
360 | steps >>= 1; | ||
361 | parent = of_get_parent(np); | ||
362 | host->num_channels = 1; | ||
363 | if (parent && parent->name[0] == 'u') { | ||
364 | host->num_channels = 2; | ||
365 | aoffset = 3; | ||
366 | } | ||
367 | /* Select interface rate */ | ||
368 | host->speed = KW_I2C_MODE_100KHZ; | ||
369 | prate = (u32 *)get_property(np, "AAPL,i2c-rate", NULL); | ||
370 | if (prate) switch(*prate) { | ||
371 | case 100: | ||
372 | host->speed = KW_I2C_MODE_100KHZ; | ||
373 | break; | ||
374 | case 50: | ||
375 | host->speed = KW_I2C_MODE_50KHZ; | ||
376 | break; | ||
377 | case 25: | ||
378 | host->speed = KW_I2C_MODE_25KHZ; | ||
379 | break; | ||
380 | } | ||
381 | |||
382 | host->mode = pmac_low_i2c_mode_std; | ||
383 | host->base = ioremap(np->addrs[0].address + aoffset, | ||
384 | np->addrs[0].size); | ||
385 | host->func = keywest_low_i2c_func; | ||
386 | } | ||
387 | |||
388 | /* | ||
389 | * | ||
390 | * PMU implementation | ||
391 | * | ||
392 | */ | ||
393 | |||
394 | |||
395 | #ifdef CONFIG_ADB_PMU | ||
396 | |||
397 | static int pmu_low_i2c_func(struct low_i2c_host *host, u8 addr, u8 sub, u8 *data, int len) | ||
398 | { | ||
399 | // TODO | ||
400 | return -ENODEV; | ||
401 | } | ||
402 | |||
403 | static void pmu_low_i2c_add(struct device_node *np) | ||
404 | { | ||
405 | struct low_i2c_host *host = find_low_i2c_host(NULL); | ||
406 | |||
407 | if (host == NULL) { | ||
408 | printk(KERN_ERR "low_i2c: Can't allocate host for %s\n", | ||
409 | np->full_name); | ||
410 | return; | ||
411 | } | ||
412 | memset(host, 0, sizeof(*host)); | ||
413 | |||
414 | init_MUTEX(&host->mutex); | ||
415 | host->np = of_node_get(np); | ||
416 | host->num_channels = 3; | ||
417 | host->mode = pmac_low_i2c_mode_std; | ||
418 | host->func = pmu_low_i2c_func; | ||
419 | } | ||
420 | |||
421 | #endif /* CONFIG_ADB_PMU */ | ||
422 | |||
423 | void __init pmac_init_low_i2c(void) | ||
424 | { | ||
425 | struct device_node *np; | ||
426 | |||
427 | /* Probe keywest-i2c busses */ | ||
428 | np = of_find_compatible_node(NULL, "i2c", "keywest-i2c"); | ||
429 | while(np) { | ||
430 | keywest_low_i2c_add(np); | ||
431 | np = of_find_compatible_node(np, "i2c", "keywest-i2c"); | ||
432 | } | ||
433 | |||
434 | #ifdef CONFIG_ADB_PMU | ||
435 | /* Probe PMU busses */ | ||
436 | np = of_find_node_by_name(NULL, "via-pmu"); | ||
437 | if (np) | ||
438 | pmu_low_i2c_add(np); | ||
439 | #endif /* CONFIG_ADB_PMU */ | ||
440 | |||
441 | /* TODO: Add CUDA support as well */ | ||
442 | } | ||
443 | |||
444 | int pmac_low_i2c_lock(struct device_node *np) | ||
445 | { | ||
446 | struct low_i2c_host *host = find_low_i2c_host(np); | ||
447 | |||
448 | if (!host) | ||
449 | return -ENODEV; | ||
450 | down(&host->mutex); | ||
451 | return 0; | ||
452 | } | ||
453 | EXPORT_SYMBOL(pmac_low_i2c_lock); | ||
454 | |||
455 | int pmac_low_i2c_unlock(struct device_node *np) | ||
456 | { | ||
457 | struct low_i2c_host *host = find_low_i2c_host(np); | ||
458 | |||
459 | if (!host) | ||
460 | return -ENODEV; | ||
461 | up(&host->mutex); | ||
462 | return 0; | ||
463 | } | ||
464 | EXPORT_SYMBOL(pmac_low_i2c_unlock); | ||
465 | |||
466 | |||
467 | int pmac_low_i2c_open(struct device_node *np, int channel) | ||
468 | { | ||
469 | struct low_i2c_host *host = find_low_i2c_host(np); | ||
470 | |||
471 | if (!host) | ||
472 | return -ENODEV; | ||
473 | |||
474 | if (channel >= host->num_channels) | ||
475 | return -EINVAL; | ||
476 | |||
477 | down(&host->mutex); | ||
478 | host->is_open = 1; | ||
479 | host->channel = channel; | ||
480 | |||
481 | return 0; | ||
482 | } | ||
483 | EXPORT_SYMBOL(pmac_low_i2c_open); | ||
484 | |||
485 | int pmac_low_i2c_close(struct device_node *np) | ||
486 | { | ||
487 | struct low_i2c_host *host = find_low_i2c_host(np); | ||
488 | |||
489 | if (!host) | ||
490 | return -ENODEV; | ||
491 | |||
492 | host->is_open = 0; | ||
493 | up(&host->mutex); | ||
494 | |||
495 | return 0; | ||
496 | } | ||
497 | EXPORT_SYMBOL(pmac_low_i2c_close); | ||
498 | |||
499 | int pmac_low_i2c_setmode(struct device_node *np, int mode) | ||
500 | { | ||
501 | struct low_i2c_host *host = find_low_i2c_host(np); | ||
502 | |||
503 | if (!host) | ||
504 | return -ENODEV; | ||
505 | WARN_ON(!host->is_open); | ||
506 | host->mode = mode; | ||
507 | |||
508 | return 0; | ||
509 | } | ||
510 | EXPORT_SYMBOL(pmac_low_i2c_setmode); | ||
511 | |||
512 | int pmac_low_i2c_xfer(struct device_node *np, u8 addrdir, u8 subaddr, u8 *data, int len) | ||
513 | { | ||
514 | struct low_i2c_host *host = find_low_i2c_host(np); | ||
515 | |||
516 | if (!host) | ||
517 | return -ENODEV; | ||
518 | WARN_ON(!host->is_open); | ||
519 | |||
520 | return host->func(host, addrdir, subaddr, data, len); | ||
521 | } | ||
522 | EXPORT_SYMBOL(pmac_low_i2c_xfer); | ||
523 | |||