diff options
author | akpm@linux-foundation.org <akpm@linux-foundation.org> | 2007-05-08 03:31:22 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-05-08 14:15:14 -0400 |
commit | f19b121e21c1b032f6c612d2b9b499151f7b661b (patch) | |
tree | 8ee059ce2d92e81b7d1fe1e2c4cd4cdf7384a921 /drivers/w1/masters/ds1wm.c | |
parent | c1f858b763de570a4ab119ade7b24ccbc8fad23a (diff) |
Driver for the Maxim DS1WM, a 1-wire bus master ASIC core
Cc: Matt Reimer <mreimer@vpop.net>
[akpm@linux-foundation.org: kconfig update]
Signed-off-by: Matt Reimer <mreimer@vpop.net>
Signed-off-by: Evgeniy Polyakov <johnpol@2ka.mipt.ru>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/w1/masters/ds1wm.c')
-rw-r--r-- | drivers/w1/masters/ds1wm.c | 468 |
1 files changed, 468 insertions, 0 deletions
diff --git a/drivers/w1/masters/ds1wm.c b/drivers/w1/masters/ds1wm.c new file mode 100644 index 000000000000..763bc73e5070 --- /dev/null +++ b/drivers/w1/masters/ds1wm.c | |||
@@ -0,0 +1,468 @@ | |||
1 | /* | ||
2 | * 1-wire busmaster driver for DS1WM and ASICs with embedded DS1WMs | ||
3 | * such as HP iPAQs (including h5xxx, h2200, and devices with ASIC3 | ||
4 | * like hx4700). | ||
5 | * | ||
6 | * Copyright (c) 2004-2005, Szabolcs Gyurko <szabolcs.gyurko@tlt.hu> | ||
7 | * Copyright (c) 2004-2007, Matt Reimer <mreimer@vpop.net> | ||
8 | * | ||
9 | * Use consistent with the GNU GPL is permitted, | ||
10 | * provided that this copyright notice is | ||
11 | * preserved in its entirety in all copies and derived works. | ||
12 | */ | ||
13 | |||
14 | #include <linux/module.h> | ||
15 | #include <linux/interrupt.h> | ||
16 | #include <linux/irq.h> | ||
17 | #include <linux/pm.h> | ||
18 | #include <linux/platform_device.h> | ||
19 | #include <linux/clk.h> | ||
20 | #include <linux/delay.h> | ||
21 | #include <linux/ds1wm.h> | ||
22 | |||
23 | #include <asm/io.h> | ||
24 | |||
25 | #include "../w1.h" | ||
26 | #include "../w1_int.h" | ||
27 | |||
28 | |||
29 | #define DS1WM_CMD 0x00 /* R/W 4 bits command */ | ||
30 | #define DS1WM_DATA 0x01 /* R/W 8 bits, transmit/receive buffer */ | ||
31 | #define DS1WM_INT 0x02 /* R/W interrupt status */ | ||
32 | #define DS1WM_INT_EN 0x03 /* R/W interrupt enable */ | ||
33 | #define DS1WM_CLKDIV 0x04 /* R/W 5 bits of divisor and pre-scale */ | ||
34 | |||
35 | #define DS1WM_CMD_1W_RESET (1 << 0) /* force reset on 1-wire bus */ | ||
36 | #define DS1WM_CMD_SRA (1 << 1) /* enable Search ROM accelerator mode */ | ||
37 | #define DS1WM_CMD_DQ_OUTPUT (1 << 2) /* write only - forces bus low */ | ||
38 | #define DS1WM_CMD_DQ_INPUT (1 << 3) /* read only - reflects state of bus */ | ||
39 | #define DS1WM_CMD_RST (1 << 5) /* software reset */ | ||
40 | #define DS1WM_CMD_OD (1 << 7) /* overdrive */ | ||
41 | |||
42 | #define DS1WM_INT_PD (1 << 0) /* presence detect */ | ||
43 | #define DS1WM_INT_PDR (1 << 1) /* presence detect result */ | ||
44 | #define DS1WM_INT_TBE (1 << 2) /* tx buffer empty */ | ||
45 | #define DS1WM_INT_TSRE (1 << 3) /* tx shift register empty */ | ||
46 | #define DS1WM_INT_RBF (1 << 4) /* rx buffer full */ | ||
47 | #define DS1WM_INT_RSRF (1 << 5) /* rx shift register full */ | ||
48 | |||
49 | #define DS1WM_INTEN_EPD (1 << 0) /* enable presence detect int */ | ||
50 | #define DS1WM_INTEN_IAS (1 << 1) /* INTR active state */ | ||
51 | #define DS1WM_INTEN_ETBE (1 << 2) /* enable tx buffer empty int */ | ||
52 | #define DS1WM_INTEN_ETMT (1 << 3) /* enable tx shift register empty int */ | ||
53 | #define DS1WM_INTEN_ERBF (1 << 4) /* enable rx buffer full int */ | ||
54 | #define DS1WM_INTEN_ERSRF (1 << 5) /* enable rx shift register full int */ | ||
55 | #define DS1WM_INTEN_DQO (1 << 6) /* enable direct bus driving ops */ | ||
56 | |||
57 | |||
58 | #define DS1WM_TIMEOUT (HZ * 5) | ||
59 | |||
60 | static struct { | ||
61 | unsigned long freq; | ||
62 | unsigned long divisor; | ||
63 | } freq[] = { | ||
64 | { 4000000, 0x8 }, | ||
65 | { 5000000, 0x2 }, | ||
66 | { 6000000, 0x5 }, | ||
67 | { 7000000, 0x3 }, | ||
68 | { 8000000, 0xc }, | ||
69 | { 10000000, 0x6 }, | ||
70 | { 12000000, 0x9 }, | ||
71 | { 14000000, 0x7 }, | ||
72 | { 16000000, 0x10 }, | ||
73 | { 20000000, 0xa }, | ||
74 | { 24000000, 0xd }, | ||
75 | { 28000000, 0xb }, | ||
76 | { 32000000, 0x14 }, | ||
77 | { 40000000, 0xe }, | ||
78 | { 48000000, 0x11 }, | ||
79 | { 56000000, 0xf }, | ||
80 | { 64000000, 0x18 }, | ||
81 | { 80000000, 0x12 }, | ||
82 | { 96000000, 0x15 }, | ||
83 | { 112000000, 0x13 }, | ||
84 | { 128000000, 0x1c }, | ||
85 | }; | ||
86 | |||
87 | struct ds1wm_data { | ||
88 | void *map; | ||
89 | int bus_shift; /* # of shifts to calc register offsets */ | ||
90 | struct platform_device *pdev; | ||
91 | struct ds1wm_platform_data *pdata; | ||
92 | int irq; | ||
93 | int active_high; | ||
94 | struct clk *clk; | ||
95 | int slave_present; | ||
96 | void *reset_complete; | ||
97 | void *read_complete; | ||
98 | void *write_complete; | ||
99 | u8 read_byte; /* last byte received */ | ||
100 | }; | ||
101 | |||
102 | static inline void ds1wm_write_register(struct ds1wm_data *ds1wm_data, u32 reg, | ||
103 | u8 val) | ||
104 | { | ||
105 | __raw_writeb(val, ds1wm_data->map + (reg << ds1wm_data->bus_shift)); | ||
106 | } | ||
107 | |||
108 | static inline u8 ds1wm_read_register(struct ds1wm_data *ds1wm_data, u32 reg) | ||
109 | { | ||
110 | return __raw_readb(ds1wm_data->map + (reg << ds1wm_data->bus_shift)); | ||
111 | } | ||
112 | |||
113 | |||
114 | static irqreturn_t ds1wm_isr(int isr, void *data) | ||
115 | { | ||
116 | struct ds1wm_data *ds1wm_data = data; | ||
117 | u8 intr = ds1wm_read_register(ds1wm_data, DS1WM_INT); | ||
118 | |||
119 | ds1wm_data->slave_present = (intr & DS1WM_INT_PDR) ? 0 : 1; | ||
120 | |||
121 | if ((intr & DS1WM_INT_PD) && ds1wm_data->reset_complete) | ||
122 | complete(ds1wm_data->reset_complete); | ||
123 | |||
124 | if ((intr & DS1WM_INT_TSRE) && ds1wm_data->write_complete) | ||
125 | complete(ds1wm_data->write_complete); | ||
126 | |||
127 | if (intr & DS1WM_INT_RBF) { | ||
128 | ds1wm_data->read_byte = ds1wm_read_register(ds1wm_data, | ||
129 | DS1WM_DATA); | ||
130 | if (ds1wm_data->read_complete) | ||
131 | complete(ds1wm_data->read_complete); | ||
132 | } | ||
133 | |||
134 | return IRQ_HANDLED; | ||
135 | } | ||
136 | |||
137 | static int ds1wm_reset(struct ds1wm_data *ds1wm_data) | ||
138 | { | ||
139 | unsigned long timeleft; | ||
140 | DECLARE_COMPLETION_ONSTACK(reset_done); | ||
141 | |||
142 | ds1wm_data->reset_complete = &reset_done; | ||
143 | |||
144 | ds1wm_write_register(ds1wm_data, DS1WM_INT_EN, DS1WM_INTEN_EPD | | ||
145 | (ds1wm_data->active_high ? DS1WM_INTEN_IAS : 0)); | ||
146 | |||
147 | ds1wm_write_register(ds1wm_data, DS1WM_CMD, DS1WM_CMD_1W_RESET); | ||
148 | |||
149 | timeleft = wait_for_completion_timeout(&reset_done, DS1WM_TIMEOUT); | ||
150 | ds1wm_data->reset_complete = NULL; | ||
151 | if (!timeleft) { | ||
152 | dev_dbg(&ds1wm_data->pdev->dev, "reset failed\n"); | ||
153 | return 1; | ||
154 | } | ||
155 | |||
156 | /* Wait for the end of the reset. According to the specs, the time | ||
157 | * from when the interrupt is asserted to the end of the reset is: | ||
158 | * tRSTH - tPDH - tPDL - tPDI | ||
159 | * 625 us - 60 us - 240 us - 100 ns = 324.9 us | ||
160 | * | ||
161 | * We'll wait a bit longer just to be sure. | ||
162 | */ | ||
163 | udelay(500); | ||
164 | |||
165 | ds1wm_write_register(ds1wm_data, DS1WM_INT_EN, | ||
166 | DS1WM_INTEN_ERBF | DS1WM_INTEN_ETMT | DS1WM_INTEN_EPD | | ||
167 | (ds1wm_data->active_high ? DS1WM_INTEN_IAS : 0)); | ||
168 | |||
169 | if (!ds1wm_data->slave_present) { | ||
170 | dev_dbg(&ds1wm_data->pdev->dev, "reset: no devices found\n"); | ||
171 | return 1; | ||
172 | } | ||
173 | |||
174 | return 0; | ||
175 | } | ||
176 | |||
177 | static int ds1wm_write(struct ds1wm_data *ds1wm_data, u8 data) | ||
178 | { | ||
179 | DECLARE_COMPLETION_ONSTACK(write_done); | ||
180 | ds1wm_data->write_complete = &write_done; | ||
181 | |||
182 | ds1wm_write_register(ds1wm_data, DS1WM_DATA, data); | ||
183 | |||
184 | wait_for_completion_timeout(&write_done, DS1WM_TIMEOUT); | ||
185 | ds1wm_data->write_complete = NULL; | ||
186 | |||
187 | return 0; | ||
188 | } | ||
189 | |||
190 | static int ds1wm_read(struct ds1wm_data *ds1wm_data, unsigned char write_data) | ||
191 | { | ||
192 | DECLARE_COMPLETION_ONSTACK(read_done); | ||
193 | ds1wm_data->read_complete = &read_done; | ||
194 | |||
195 | ds1wm_write(ds1wm_data, write_data); | ||
196 | wait_for_completion_timeout(&read_done, DS1WM_TIMEOUT); | ||
197 | ds1wm_data->read_complete = NULL; | ||
198 | |||
199 | return ds1wm_data->read_byte; | ||
200 | } | ||
201 | |||
202 | static int ds1wm_find_divisor(int gclk) | ||
203 | { | ||
204 | int i; | ||
205 | |||
206 | for (i = 0; i < ARRAY_SIZE(freq); i++) | ||
207 | if (gclk <= freq[i].freq) | ||
208 | return freq[i].divisor; | ||
209 | |||
210 | return 0; | ||
211 | } | ||
212 | |||
213 | static void ds1wm_up(struct ds1wm_data *ds1wm_data) | ||
214 | { | ||
215 | int gclk, divisor; | ||
216 | |||
217 | if (ds1wm_data->pdata->enable) | ||
218 | ds1wm_data->pdata->enable(ds1wm_data->pdev); | ||
219 | |||
220 | gclk = clk_get_rate(ds1wm_data->clk); | ||
221 | clk_enable(ds1wm_data->clk); | ||
222 | divisor = ds1wm_find_divisor(gclk); | ||
223 | if (divisor == 0) { | ||
224 | dev_err(&ds1wm_data->pdev->dev, | ||
225 | "no suitable divisor for %dHz clock\n", gclk); | ||
226 | return; | ||
227 | } | ||
228 | ds1wm_write_register(ds1wm_data, DS1WM_CLKDIV, divisor); | ||
229 | |||
230 | /* Let the w1 clock stabilize. */ | ||
231 | msleep(1); | ||
232 | |||
233 | ds1wm_reset(ds1wm_data); | ||
234 | } | ||
235 | |||
236 | static void ds1wm_down(struct ds1wm_data *ds1wm_data) | ||
237 | { | ||
238 | ds1wm_reset(ds1wm_data); | ||
239 | |||
240 | /* Disable interrupts. */ | ||
241 | ds1wm_write_register(ds1wm_data, DS1WM_INT_EN, | ||
242 | ds1wm_data->active_high ? DS1WM_INTEN_IAS : 0); | ||
243 | |||
244 | if (ds1wm_data->pdata->disable) | ||
245 | ds1wm_data->pdata->disable(ds1wm_data->pdev); | ||
246 | |||
247 | clk_disable(ds1wm_data->clk); | ||
248 | } | ||
249 | |||
250 | /* --------------------------------------------------------------------- */ | ||
251 | /* w1 methods */ | ||
252 | |||
253 | static u8 ds1wm_read_byte(void *data) | ||
254 | { | ||
255 | struct ds1wm_data *ds1wm_data = data; | ||
256 | |||
257 | return ds1wm_read(ds1wm_data, 0xff); | ||
258 | } | ||
259 | |||
260 | static void ds1wm_write_byte(void *data, u8 byte) | ||
261 | { | ||
262 | struct ds1wm_data *ds1wm_data = data; | ||
263 | |||
264 | ds1wm_write(ds1wm_data, byte); | ||
265 | } | ||
266 | |||
267 | static u8 ds1wm_reset_bus(void *data) | ||
268 | { | ||
269 | struct ds1wm_data *ds1wm_data = data; | ||
270 | |||
271 | ds1wm_reset(ds1wm_data); | ||
272 | |||
273 | return 0; | ||
274 | } | ||
275 | |||
276 | static void ds1wm_search(void *data, u8 search_type, | ||
277 | w1_slave_found_callback slave_found) | ||
278 | { | ||
279 | struct ds1wm_data *ds1wm_data = data; | ||
280 | int i; | ||
281 | unsigned long long rom_id; | ||
282 | |||
283 | /* XXX We need to iterate for multiple devices per the DS1WM docs. | ||
284 | * See http://www.maxim-ic.com/appnotes.cfm/appnote_number/120. */ | ||
285 | if (ds1wm_reset(ds1wm_data)) | ||
286 | return; | ||
287 | |||
288 | ds1wm_write(ds1wm_data, search_type); | ||
289 | ds1wm_write_register(ds1wm_data, DS1WM_CMD, DS1WM_CMD_SRA); | ||
290 | |||
291 | for (rom_id = 0, i = 0; i < 16; i++) { | ||
292 | |||
293 | unsigned char resp, r, d; | ||
294 | |||
295 | resp = ds1wm_read(ds1wm_data, 0x00); | ||
296 | |||
297 | r = ((resp & 0x02) >> 1) | | ||
298 | ((resp & 0x08) >> 2) | | ||
299 | ((resp & 0x20) >> 3) | | ||
300 | ((resp & 0x80) >> 4); | ||
301 | |||
302 | d = ((resp & 0x01) >> 0) | | ||
303 | ((resp & 0x04) >> 1) | | ||
304 | ((resp & 0x10) >> 2) | | ||
305 | ((resp & 0x40) >> 3); | ||
306 | |||
307 | rom_id |= (unsigned long long) r << (i * 4); | ||
308 | |||
309 | } | ||
310 | dev_dbg(&ds1wm_data->pdev->dev, "found 0x%08llX", rom_id); | ||
311 | |||
312 | ds1wm_write_register(ds1wm_data, DS1WM_CMD, ~DS1WM_CMD_SRA); | ||
313 | ds1wm_reset(ds1wm_data); | ||
314 | |||
315 | slave_found(ds1wm_data, rom_id); | ||
316 | } | ||
317 | |||
318 | /* --------------------------------------------------------------------- */ | ||
319 | |||
320 | static struct w1_bus_master ds1wm_master = { | ||
321 | .read_byte = ds1wm_read_byte, | ||
322 | .write_byte = ds1wm_write_byte, | ||
323 | .reset_bus = ds1wm_reset_bus, | ||
324 | .search = ds1wm_search, | ||
325 | }; | ||
326 | |||
327 | static int ds1wm_probe(struct platform_device *pdev) | ||
328 | { | ||
329 | struct ds1wm_data *ds1wm_data; | ||
330 | struct ds1wm_platform_data *plat; | ||
331 | struct resource *res; | ||
332 | int ret; | ||
333 | |||
334 | if (!pdev) | ||
335 | return -ENODEV; | ||
336 | |||
337 | ds1wm_data = kzalloc(sizeof (*ds1wm_data), GFP_KERNEL); | ||
338 | if (!ds1wm_data) | ||
339 | return -ENOMEM; | ||
340 | |||
341 | platform_set_drvdata(pdev, ds1wm_data); | ||
342 | |||
343 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
344 | if (!res) { | ||
345 | ret = -ENXIO; | ||
346 | goto err0; | ||
347 | } | ||
348 | ds1wm_data->map = ioremap(res->start, res->end - res->start + 1); | ||
349 | if (!ds1wm_data->map) { | ||
350 | ret = -ENOMEM; | ||
351 | goto err0; | ||
352 | } | ||
353 | plat = pdev->dev.platform_data; | ||
354 | ds1wm_data->bus_shift = plat->bus_shift; | ||
355 | ds1wm_data->pdev = pdev; | ||
356 | ds1wm_data->pdata = plat; | ||
357 | |||
358 | res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); | ||
359 | if (!res) { | ||
360 | ret = -ENXIO; | ||
361 | goto err1; | ||
362 | } | ||
363 | ds1wm_data->irq = res->start; | ||
364 | ds1wm_data->active_high = (res->flags & IORESOURCE_IRQ_HIGHEDGE) ? | ||
365 | 1 : 0; | ||
366 | |||
367 | set_irq_type(ds1wm_data->irq, ds1wm_data->active_high ? | ||
368 | IRQ_TYPE_EDGE_RISING : IRQ_TYPE_EDGE_FALLING); | ||
369 | |||
370 | ret = request_irq(ds1wm_data->irq, ds1wm_isr, IRQF_DISABLED, | ||
371 | "ds1wm", ds1wm_data); | ||
372 | if (ret) | ||
373 | goto err1; | ||
374 | |||
375 | ds1wm_data->clk = clk_get(&pdev->dev, "ds1wm"); | ||
376 | if (!ds1wm_data->clk) { | ||
377 | ret = -ENOENT; | ||
378 | goto err2; | ||
379 | } | ||
380 | |||
381 | ds1wm_up(ds1wm_data); | ||
382 | |||
383 | ds1wm_master.data = (void *)ds1wm_data; | ||
384 | |||
385 | ret = w1_add_master_device(&ds1wm_master); | ||
386 | if (ret) | ||
387 | goto err3; | ||
388 | |||
389 | return 0; | ||
390 | |||
391 | err3: | ||
392 | ds1wm_down(ds1wm_data); | ||
393 | clk_put(ds1wm_data->clk); | ||
394 | err2: | ||
395 | free_irq(ds1wm_data->irq, ds1wm_data); | ||
396 | err1: | ||
397 | iounmap(ds1wm_data->map); | ||
398 | err0: | ||
399 | kfree(ds1wm_data); | ||
400 | |||
401 | return ret; | ||
402 | } | ||
403 | |||
404 | #ifdef CONFIG_PM | ||
405 | static int ds1wm_suspend(struct platform_device *pdev, pm_message_t state) | ||
406 | { | ||
407 | struct ds1wm_data *ds1wm_data = platform_get_drvdata(pdev); | ||
408 | |||
409 | ds1wm_down(ds1wm_data); | ||
410 | |||
411 | return 0; | ||
412 | } | ||
413 | |||
414 | static int ds1wm_resume(struct platform_device *pdev) | ||
415 | { | ||
416 | struct ds1wm_data *ds1wm_data = platform_get_drvdata(pdev); | ||
417 | |||
418 | ds1wm_up(ds1wm_data); | ||
419 | |||
420 | return 0; | ||
421 | } | ||
422 | #else | ||
423 | #define ds1wm_suspend NULL | ||
424 | #define ds1wm_resume NULL | ||
425 | #endif | ||
426 | |||
427 | static int ds1wm_remove(struct platform_device *pdev) | ||
428 | { | ||
429 | struct ds1wm_data *ds1wm_data = platform_get_drvdata(pdev); | ||
430 | |||
431 | w1_remove_master_device(&ds1wm_master); | ||
432 | ds1wm_down(ds1wm_data); | ||
433 | clk_put(ds1wm_data->clk); | ||
434 | free_irq(ds1wm_data->irq, ds1wm_data); | ||
435 | iounmap(ds1wm_data->map); | ||
436 | kfree(ds1wm_data); | ||
437 | |||
438 | return 0; | ||
439 | } | ||
440 | |||
441 | static struct platform_driver ds1wm_driver = { | ||
442 | .driver = { | ||
443 | .name = "ds1wm", | ||
444 | }, | ||
445 | .probe = ds1wm_probe, | ||
446 | .remove = ds1wm_remove, | ||
447 | .suspend = ds1wm_suspend, | ||
448 | .resume = ds1wm_resume | ||
449 | }; | ||
450 | |||
451 | static int __init ds1wm_init(void) | ||
452 | { | ||
453 | printk("DS1WM w1 busmaster driver - (c) 2004 Szabolcs Gyurko\n"); | ||
454 | return platform_driver_register(&ds1wm_driver); | ||
455 | } | ||
456 | |||
457 | static void __exit ds1wm_exit(void) | ||
458 | { | ||
459 | platform_driver_unregister(&ds1wm_driver); | ||
460 | } | ||
461 | |||
462 | module_init(ds1wm_init); | ||
463 | module_exit(ds1wm_exit); | ||
464 | |||
465 | MODULE_LICENSE("GPL"); | ||
466 | MODULE_AUTHOR("Szabolcs Gyurko <szabolcs.gyurko@tlt.hu>, " | ||
467 | "Matt Reimer <mreimer@vpop.net>"); | ||
468 | MODULE_DESCRIPTION("DS1WM w1 busmaster driver"); | ||