Commit | Line | Data |
---|---|---|
e21e245b | 1 | /* bbc_i2c.c: I2C low-level driver for BBC device on UltraSPARC-III |
1da177e4 LT |
2 | * platforms. |
3 | * | |
e21e245b | 4 | * Copyright (C) 2001, 2008 David S. Miller (davem@davemloft.net) |
1da177e4 LT |
5 | */ |
6 | ||
7 | #include <linux/module.h> | |
8 | #include <linux/kernel.h> | |
9 | #include <linux/types.h> | |
10 | #include <linux/slab.h> | |
11 | #include <linux/sched.h> | |
12 | #include <linux/wait.h> | |
13 | #include <linux/delay.h> | |
1da177e4 | 14 | #include <linux/interrupt.h> |
e21e245b DM |
15 | #include <linux/of.h> |
16 | #include <linux/of_device.h> | |
1da177e4 | 17 | #include <asm/bbc.h> |
4b5dff76 | 18 | #include <asm/io.h> |
1da177e4 LT |
19 | |
20 | #include "bbc_i2c.h" | |
21 | ||
22 | /* Convert this driver to use i2c bus layer someday... */ | |
23 | #define I2C_PCF_PIN 0x80 | |
24 | #define I2C_PCF_ESO 0x40 | |
25 | #define I2C_PCF_ES1 0x20 | |
26 | #define I2C_PCF_ES2 0x10 | |
27 | #define I2C_PCF_ENI 0x08 | |
28 | #define I2C_PCF_STA 0x04 | |
29 | #define I2C_PCF_STO 0x02 | |
30 | #define I2C_PCF_ACK 0x01 | |
31 | ||
32 | #define I2C_PCF_START (I2C_PCF_PIN | I2C_PCF_ESO | I2C_PCF_ENI | I2C_PCF_STA | I2C_PCF_ACK) | |
33 | #define I2C_PCF_STOP (I2C_PCF_PIN | I2C_PCF_ESO | I2C_PCF_STO | I2C_PCF_ACK) | |
34 | #define I2C_PCF_REPSTART ( I2C_PCF_ESO | I2C_PCF_STA | I2C_PCF_ACK) | |
35 | #define I2C_PCF_IDLE (I2C_PCF_PIN | I2C_PCF_ESO | I2C_PCF_ACK) | |
36 | ||
37 | #define I2C_PCF_INI 0x40 /* 1 if not initialized */ | |
38 | #define I2C_PCF_STS 0x20 | |
39 | #define I2C_PCF_BER 0x10 | |
40 | #define I2C_PCF_AD0 0x08 | |
41 | #define I2C_PCF_LRB 0x08 | |
42 | #define I2C_PCF_AAS 0x04 | |
43 | #define I2C_PCF_LAB 0x02 | |
44 | #define I2C_PCF_BB 0x01 | |
45 | ||
46 | /* The BBC devices have two I2C controllers. The first I2C controller | |
47 | * connects mainly to configuration proms (NVRAM, cpu configuration, | |
48 | * dimm types, etc.). Whereas the second I2C controller connects to | |
49 | * environmental control devices such as fans and temperature sensors. | |
50 | * The second controller also connects to the smartcard reader, if present. | |
51 | */ | |
52 | ||
2dc11581 | 53 | static void set_device_claimage(struct bbc_i2c_bus *bp, struct platform_device *op, int val) |
1da177e4 LT |
54 | { |
55 | int i; | |
56 | ||
57 | for (i = 0; i < NUM_CHILDREN; i++) { | |
e21e245b | 58 | if (bp->devs[i].device == op) { |
1da177e4 LT |
59 | bp->devs[i].client_claimed = val; |
60 | return; | |
61 | } | |
62 | } | |
63 | } | |
64 | ||
65 | #define claim_device(BP,ECHILD) set_device_claimage(BP,ECHILD,1) | |
66 | #define release_device(BP,ECHILD) set_device_claimage(BP,ECHILD,0) | |
67 | ||
2dc11581 | 68 | struct platform_device *bbc_i2c_getdev(struct bbc_i2c_bus *bp, int index) |
1da177e4 | 69 | { |
2dc11581 | 70 | struct platform_device *op = NULL; |
e21e245b | 71 | int curidx = 0, i; |
1da177e4 | 72 | |
e21e245b DM |
73 | for (i = 0; i < NUM_CHILDREN; i++) { |
74 | if (!(op = bp->devs[i].device)) | |
1da177e4 | 75 | break; |
e21e245b DM |
76 | if (curidx == index) |
77 | goto out; | |
78 | op = NULL; | |
79 | curidx++; | |
1da177e4 LT |
80 | } |
81 | ||
1da177e4 LT |
82 | out: |
83 | if (curidx == index) | |
e21e245b | 84 | return op; |
1da177e4 LT |
85 | return NULL; |
86 | } | |
87 | ||
2dc11581 | 88 | struct bbc_i2c_client *bbc_i2c_attach(struct bbc_i2c_bus *bp, struct platform_device *op) |
1da177e4 | 89 | { |
1da177e4 | 90 | struct bbc_i2c_client *client; |
e21e245b | 91 | const u32 *reg; |
1da177e4 | 92 | |
dd00cc48 | 93 | client = kzalloc(sizeof(*client), GFP_KERNEL); |
1da177e4 LT |
94 | if (!client) |
95 | return NULL; | |
1da177e4 | 96 | client->bp = bp; |
e21e245b DM |
97 | client->op = op; |
98 | ||
61c7a080 | 99 | reg = of_get_property(op->dev.of_node, "reg", NULL); |
e21e245b DM |
100 | if (!reg) { |
101 | kfree(client); | |
102 | return NULL; | |
103 | } | |
1da177e4 | 104 | |
e21e245b DM |
105 | client->bus = reg[0]; |
106 | client->address = reg[1]; | |
107 | ||
108 | claim_device(bp, op); | |
1da177e4 LT |
109 | |
110 | return client; | |
111 | } | |
112 | ||
113 | void bbc_i2c_detach(struct bbc_i2c_client *client) | |
114 | { | |
115 | struct bbc_i2c_bus *bp = client->bp; | |
2dc11581 | 116 | struct platform_device *op = client->op; |
1da177e4 | 117 | |
e21e245b | 118 | release_device(bp, op); |
1da177e4 LT |
119 | kfree(client); |
120 | } | |
121 | ||
122 | static int wait_for_pin(struct bbc_i2c_bus *bp, u8 *status) | |
123 | { | |
124 | DECLARE_WAITQUEUE(wait, current); | |
125 | int limit = 32; | |
126 | int ret = 1; | |
127 | ||
128 | bp->waiting = 1; | |
129 | add_wait_queue(&bp->wq, &wait); | |
130 | while (limit-- > 0) { | |
f4c13638 | 131 | long val; |
3b36fb84 DM |
132 | |
133 | val = wait_event_interruptible_timeout( | |
134 | bp->wq, | |
135 | (((*status = readb(bp->i2c_control_regs + 0)) | |
136 | & I2C_PCF_PIN) == 0), | |
137 | msecs_to_jiffies(250)); | |
138 | if (val > 0) { | |
1da177e4 LT |
139 | ret = 0; |
140 | break; | |
141 | } | |
1da177e4 LT |
142 | } |
143 | remove_wait_queue(&bp->wq, &wait); | |
144 | bp->waiting = 0; | |
1da177e4 LT |
145 | |
146 | return ret; | |
147 | } | |
148 | ||
149 | int bbc_i2c_writeb(struct bbc_i2c_client *client, unsigned char val, int off) | |
150 | { | |
151 | struct bbc_i2c_bus *bp = client->bp; | |
152 | int address = client->address; | |
153 | u8 status; | |
154 | int ret = -1; | |
155 | ||
156 | if (bp->i2c_bussel_reg != NULL) | |
157 | writeb(client->bus, bp->i2c_bussel_reg); | |
158 | ||
159 | writeb(address, bp->i2c_control_regs + 0x1); | |
160 | writeb(I2C_PCF_START, bp->i2c_control_regs + 0x0); | |
161 | if (wait_for_pin(bp, &status)) | |
162 | goto out; | |
163 | ||
164 | writeb(off, bp->i2c_control_regs + 0x1); | |
165 | if (wait_for_pin(bp, &status) || | |
166 | (status & I2C_PCF_LRB) != 0) | |
167 | goto out; | |
168 | ||
169 | writeb(val, bp->i2c_control_regs + 0x1); | |
170 | if (wait_for_pin(bp, &status)) | |
171 | goto out; | |
172 | ||
173 | ret = 0; | |
174 | ||
175 | out: | |
176 | writeb(I2C_PCF_STOP, bp->i2c_control_regs + 0x0); | |
177 | return ret; | |
178 | } | |
179 | ||
180 | int bbc_i2c_readb(struct bbc_i2c_client *client, unsigned char *byte, int off) | |
181 | { | |
182 | struct bbc_i2c_bus *bp = client->bp; | |
183 | unsigned char address = client->address, status; | |
184 | int ret = -1; | |
185 | ||
186 | if (bp->i2c_bussel_reg != NULL) | |
187 | writeb(client->bus, bp->i2c_bussel_reg); | |
188 | ||
189 | writeb(address, bp->i2c_control_regs + 0x1); | |
190 | writeb(I2C_PCF_START, bp->i2c_control_regs + 0x0); | |
191 | if (wait_for_pin(bp, &status)) | |
192 | goto out; | |
193 | ||
194 | writeb(off, bp->i2c_control_regs + 0x1); | |
195 | if (wait_for_pin(bp, &status) || | |
196 | (status & I2C_PCF_LRB) != 0) | |
197 | goto out; | |
198 | ||
199 | writeb(I2C_PCF_STOP, bp->i2c_control_regs + 0x0); | |
200 | ||
201 | address |= 0x1; /* READ */ | |
202 | ||
203 | writeb(address, bp->i2c_control_regs + 0x1); | |
204 | writeb(I2C_PCF_START, bp->i2c_control_regs + 0x0); | |
205 | if (wait_for_pin(bp, &status)) | |
206 | goto out; | |
207 | ||
208 | /* Set PIN back to one so the device sends the first | |
209 | * byte. | |
210 | */ | |
211 | (void) readb(bp->i2c_control_regs + 0x1); | |
212 | if (wait_for_pin(bp, &status)) | |
213 | goto out; | |
214 | ||
215 | writeb(I2C_PCF_ESO | I2C_PCF_ENI, bp->i2c_control_regs + 0x0); | |
216 | *byte = readb(bp->i2c_control_regs + 0x1); | |
217 | if (wait_for_pin(bp, &status)) | |
218 | goto out; | |
219 | ||
220 | ret = 0; | |
221 | ||
222 | out: | |
223 | writeb(I2C_PCF_STOP, bp->i2c_control_regs + 0x0); | |
224 | (void) readb(bp->i2c_control_regs + 0x1); | |
225 | ||
226 | return ret; | |
227 | } | |
228 | ||
229 | int bbc_i2c_write_buf(struct bbc_i2c_client *client, | |
230 | char *buf, int len, int off) | |
231 | { | |
232 | int ret = 0; | |
233 | ||
234 | while (len > 0) { | |
e4104710 AL |
235 | ret = bbc_i2c_writeb(client, *buf, off); |
236 | if (ret < 0) | |
1da177e4 | 237 | break; |
1da177e4 LT |
238 | len--; |
239 | buf++; | |
240 | off++; | |
241 | } | |
242 | return ret; | |
243 | } | |
244 | ||
245 | int bbc_i2c_read_buf(struct bbc_i2c_client *client, | |
246 | char *buf, int len, int off) | |
247 | { | |
248 | int ret = 0; | |
249 | ||
250 | while (len > 0) { | |
e4104710 AL |
251 | ret = bbc_i2c_readb(client, buf, off); |
252 | if (ret < 0) | |
1da177e4 | 253 | break; |
1da177e4 LT |
254 | len--; |
255 | buf++; | |
256 | off++; | |
257 | } | |
258 | ||
259 | return ret; | |
260 | } | |
261 | ||
262 | EXPORT_SYMBOL(bbc_i2c_getdev); | |
263 | EXPORT_SYMBOL(bbc_i2c_attach); | |
264 | EXPORT_SYMBOL(bbc_i2c_detach); | |
265 | EXPORT_SYMBOL(bbc_i2c_writeb); | |
266 | EXPORT_SYMBOL(bbc_i2c_readb); | |
267 | EXPORT_SYMBOL(bbc_i2c_write_buf); | |
268 | EXPORT_SYMBOL(bbc_i2c_read_buf); | |
269 | ||
7d12e780 | 270 | static irqreturn_t bbc_i2c_interrupt(int irq, void *dev_id) |
1da177e4 LT |
271 | { |
272 | struct bbc_i2c_bus *bp = dev_id; | |
273 | ||
274 | /* PIN going from set to clear is the only event which | |
275 | * makes the i2c assert an interrupt. | |
276 | */ | |
277 | if (bp->waiting && | |
278 | !(readb(bp->i2c_control_regs + 0x0) & I2C_PCF_PIN)) | |
3b36fb84 | 279 | wake_up_interruptible(&bp->wq); |
1da177e4 LT |
280 | |
281 | return IRQ_HANDLED; | |
282 | } | |
283 | ||
6e51f857 | 284 | static void reset_one_i2c(struct bbc_i2c_bus *bp) |
1da177e4 LT |
285 | { |
286 | writeb(I2C_PCF_PIN, bp->i2c_control_regs + 0x0); | |
287 | writeb(bp->own, bp->i2c_control_regs + 0x1); | |
288 | writeb(I2C_PCF_PIN | I2C_PCF_ES1, bp->i2c_control_regs + 0x0); | |
289 | writeb(bp->clock, bp->i2c_control_regs + 0x1); | |
290 | writeb(I2C_PCF_IDLE, bp->i2c_control_regs + 0x0); | |
291 | } | |
292 | ||
6e51f857 | 293 | static struct bbc_i2c_bus * attach_one_i2c(struct platform_device *op, int index) |
1da177e4 | 294 | { |
50aa485e | 295 | struct bbc_i2c_bus *bp; |
e21e245b | 296 | struct device_node *dp; |
1da177e4 LT |
297 | int entry; |
298 | ||
50aa485e | 299 | bp = kzalloc(sizeof(*bp), GFP_KERNEL); |
1da177e4 | 300 | if (!bp) |
e21e245b | 301 | return NULL; |
1da177e4 | 302 | |
5cdceab3 CATS |
303 | INIT_LIST_HEAD(&bp->temps); |
304 | INIT_LIST_HEAD(&bp->fans); | |
305 | ||
e21e245b | 306 | bp->i2c_control_regs = of_ioremap(&op->resource[0], 0, 0x2, "bbc_i2c_regs"); |
1da177e4 LT |
307 | if (!bp->i2c_control_regs) |
308 | goto fail; | |
309 | ||
5cdceab3 CATS |
310 | if (op->num_resources == 2) { |
311 | bp->i2c_bussel_reg = of_ioremap(&op->resource[1], 0, 0x1, "bbc_i2c_bussel"); | |
312 | if (!bp->i2c_bussel_reg) | |
313 | goto fail; | |
314 | } | |
1da177e4 LT |
315 | |
316 | bp->waiting = 0; | |
317 | init_waitqueue_head(&bp->wq); | |
1636f8ac | 318 | if (request_irq(op->archdata.irqs[0], bbc_i2c_interrupt, |
dace1453 | 319 | IRQF_SHARED, "bbc_i2c", bp)) |
1da177e4 LT |
320 | goto fail; |
321 | ||
322 | bp->index = index; | |
e21e245b | 323 | bp->op = op; |
1da177e4 LT |
324 | |
325 | spin_lock_init(&bp->lock); | |
1da177e4 LT |
326 | |
327 | entry = 0; | |
61c7a080 | 328 | for (dp = op->dev.of_node->child; |
e21e245b DM |
329 | dp && entry < 8; |
330 | dp = dp->sibling, entry++) { | |
2dc11581 | 331 | struct platform_device *child_op; |
e21e245b DM |
332 | |
333 | child_op = of_find_device_by_node(dp); | |
334 | bp->devs[entry].device = child_op; | |
1da177e4 LT |
335 | bp->devs[entry].client_claimed = 0; |
336 | } | |
337 | ||
338 | writeb(I2C_PCF_PIN, bp->i2c_control_regs + 0x0); | |
339 | bp->own = readb(bp->i2c_control_regs + 0x01); | |
340 | writeb(I2C_PCF_PIN | I2C_PCF_ES1, bp->i2c_control_regs + 0x0); | |
341 | bp->clock = readb(bp->i2c_control_regs + 0x01); | |
342 | ||
343 | printk(KERN_INFO "i2c-%d: Regs at %p, %d devices, own %02x, clock %02x.\n", | |
344 | bp->index, bp->i2c_control_regs, entry, bp->own, bp->clock); | |
345 | ||
346 | reset_one_i2c(bp); | |
347 | ||
e21e245b | 348 | return bp; |
1da177e4 LT |
349 | |
350 | fail: | |
351 | if (bp->i2c_bussel_reg) | |
e21e245b | 352 | of_iounmap(&op->resource[1], bp->i2c_bussel_reg, 1); |
1da177e4 | 353 | if (bp->i2c_control_regs) |
e21e245b | 354 | of_iounmap(&op->resource[0], bp->i2c_control_regs, 2); |
1da177e4 | 355 | kfree(bp); |
e21e245b | 356 | return NULL; |
1da177e4 LT |
357 | } |
358 | ||
e21e245b DM |
359 | extern int bbc_envctrl_init(struct bbc_i2c_bus *bp); |
360 | extern void bbc_envctrl_cleanup(struct bbc_i2c_bus *bp); | |
1da177e4 | 361 | |
082a2004 | 362 | static int bbc_i2c_probe(struct platform_device *op) |
1da177e4 | 363 | { |
e21e245b | 364 | struct bbc_i2c_bus *bp; |
1da177e4 LT |
365 | int err, index = 0; |
366 | ||
e21e245b DM |
367 | bp = attach_one_i2c(op, index); |
368 | if (!bp) | |
369 | return -EINVAL; | |
1da177e4 | 370 | |
e21e245b DM |
371 | err = bbc_envctrl_init(bp); |
372 | if (err) { | |
1636f8ac | 373 | free_irq(op->archdata.irqs[0], bp); |
e21e245b DM |
374 | if (bp->i2c_bussel_reg) |
375 | of_iounmap(&op->resource[0], bp->i2c_bussel_reg, 1); | |
376 | if (bp->i2c_control_regs) | |
377 | of_iounmap(&op->resource[1], bp->i2c_control_regs, 2); | |
378 | kfree(bp); | |
379 | } else { | |
380 | dev_set_drvdata(&op->dev, bp); | |
1da177e4 LT |
381 | } |
382 | ||
1da177e4 LT |
383 | return err; |
384 | } | |
385 | ||
082a2004 | 386 | static int bbc_i2c_remove(struct platform_device *op) |
1da177e4 | 387 | { |
e21e245b DM |
388 | struct bbc_i2c_bus *bp = dev_get_drvdata(&op->dev); |
389 | ||
390 | bbc_envctrl_cleanup(bp); | |
391 | ||
1636f8ac | 392 | free_irq(op->archdata.irqs[0], bp); |
1da177e4 | 393 | |
e21e245b DM |
394 | if (bp->i2c_bussel_reg) |
395 | of_iounmap(&op->resource[0], bp->i2c_bussel_reg, 1); | |
396 | if (bp->i2c_control_regs) | |
397 | of_iounmap(&op->resource[1], bp->i2c_control_regs, 2); | |
1da177e4 | 398 | |
e21e245b | 399 | kfree(bp); |
1da177e4 | 400 | |
e21e245b DM |
401 | return 0; |
402 | } | |
1da177e4 | 403 | |
fd098316 | 404 | static const struct of_device_id bbc_i2c_match[] = { |
e21e245b DM |
405 | { |
406 | .name = "i2c", | |
407 | .compatible = "SUNW,bbc-i2c", | |
408 | }, | |
409 | {}, | |
410 | }; | |
411 | MODULE_DEVICE_TABLE(of, bbc_i2c_match); | |
1da177e4 | 412 | |
4ebb24f7 | 413 | static struct platform_driver bbc_i2c_driver = { |
4018294b GL |
414 | .driver = { |
415 | .name = "bbc_i2c", | |
4018294b GL |
416 | .of_match_table = bbc_i2c_match, |
417 | }, | |
e21e245b | 418 | .probe = bbc_i2c_probe, |
082a2004 | 419 | .remove = bbc_i2c_remove, |
e21e245b | 420 | }; |
1da177e4 | 421 | |
dbf2b92d | 422 | module_platform_driver(bbc_i2c_driver); |
e21e245b | 423 | |
b5e7ae5d | 424 | MODULE_LICENSE("GPL"); |