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