source: MondoRescue/branches/3.3/mindi-busybox/miscutils/i2c_tools.c@ 3865

Last change on this file since 3865 was 3621, checked in by Bruno Cornec, 10 years ago

New 3?3 banch for incorporation of latest busybox 1.25. Changing minor version to handle potential incompatibilities.

  • Property svn:eol-style set to native
File size: 32.5 KB
RevLine 
[3621]1/* vi: set sw=4 ts=4: */
2/*
3 * Minimal i2c-tools implementation for busybox.
4 * Parts of code ported from i2c-tools:
5 * http://www.lm-sensors.org/wiki/I2CTools.
6 *
7 * Copyright (C) 2014 by Bartosz Golaszewski <bartekgola@gmail.com>
8 *
9 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
10 */
11
12//config:config I2CGET
13//config: bool "i2cget"
14//config: default y
15//config: select PLATFORM_LINUX
16//config: help
17//config: Read from I2C/SMBus chip registers.
18//config:
19//config:config I2CSET
20//config: bool "i2cset"
21//config: default y
22//config: select PLATFORM_LINUX
23//config: help
24//config: Set I2C registers.
25//config:
26//config:config I2CDUMP
27//config: bool "i2cdump"
28//config: default y
29//config: select PLATFORM_LINUX
30//config: help
31//config: Examine I2C registers.
32//config:
33//config:config I2CDETECT
34//config: bool "i2cdetect"
35//config: default y
36//config: select PLATFORM_LINUX
37//config: help
38//config: Detect I2C chips.
39//config:
40
41//applet:IF_I2CGET(APPLET(i2cget, BB_DIR_USR_SBIN, BB_SUID_DROP))
42//applet:IF_I2CSET(APPLET(i2cset, BB_DIR_USR_SBIN, BB_SUID_DROP))
43//applet:IF_I2CDUMP(APPLET(i2cdump, BB_DIR_USR_SBIN, BB_SUID_DROP))
44//applet:IF_I2CDETECT(APPLET(i2cdetect, BB_DIR_USR_SBIN, BB_SUID_DROP))
45
46//kbuild:lib-$(CONFIG_I2CGET) += i2c_tools.o
47//kbuild:lib-$(CONFIG_I2CSET) += i2c_tools.o
48//kbuild:lib-$(CONFIG_I2CDUMP) += i2c_tools.o
49//kbuild:lib-$(CONFIG_I2CDETECT) += i2c_tools.o
50
51/*
52 * Unsupported stuff:
53 *
54 * - upstream i2c-tools can also look-up i2c busses by name, we only accept
55 * numbers,
56 * - bank and bankreg parameters for i2cdump are not supported because of
57 * their limited usefulness (see i2cdump manual entry for more info),
58 * - i2cdetect doesn't look for bus info in /proc as it does in upstream, but
59 * it shouldn't be a problem in modern kernels.
60 */
61
62#include "libbb.h"
63#include "common_bufsiz.h"
64
65#include <linux/i2c.h>
66#include <linux/i2c-dev.h>
67
68#define I2CDUMP_NUM_REGS 256
69
70#define I2CDETECT_MODE_AUTO 0
71#define I2CDETECT_MODE_QUICK 1
72#define I2CDETECT_MODE_READ 2
73
74/*
75 * This is needed for ioctl_or_perror_and_die() since it only accepts pointers.
76 */
77static ALWAYS_INLINE void *itoptr(int i)
78{
79 return (void*)(intptr_t)i;
80}
81
82static int32_t i2c_smbus_access(int fd, char read_write, uint8_t cmd,
83 int size, union i2c_smbus_data *data)
84{
85 struct i2c_smbus_ioctl_data args;
86
87 args.read_write = read_write;
88 args.command = cmd;
89 args.size = size;
90 args.data = data;
91
92 return ioctl(fd, I2C_SMBUS, &args);
93}
94
95static int32_t i2c_smbus_read_byte(int fd)
96{
97 union i2c_smbus_data data;
98 int err;
99
100 err = i2c_smbus_access(fd, I2C_SMBUS_READ, 0, I2C_SMBUS_BYTE, &data);
101 if (err < 0)
102 return err;
103
104 return data.byte;
105}
106
107#if ENABLE_I2CGET || ENABLE_I2CSET || ENABLE_I2CDUMP
108static int32_t i2c_smbus_write_byte(int fd, uint8_t val)
109{
110 return i2c_smbus_access(fd, I2C_SMBUS_WRITE,
111 val, I2C_SMBUS_BYTE, NULL);
112}
113
114static int32_t i2c_smbus_read_byte_data(int fd, uint8_t cmd)
115{
116 union i2c_smbus_data data;
117 int err;
118
119 err = i2c_smbus_access(fd, I2C_SMBUS_READ, cmd,
120 I2C_SMBUS_BYTE_DATA, &data);
121 if (err < 0)
122 return err;
123
124 return data.byte;
125}
126
127static int32_t i2c_smbus_read_word_data(int fd, uint8_t cmd)
128{
129 union i2c_smbus_data data;
130 int err;
131
132 err = i2c_smbus_access(fd, I2C_SMBUS_READ, cmd,
133 I2C_SMBUS_WORD_DATA, &data);
134 if (err < 0)
135 return err;
136
137 return data.word;
138}
139#endif /* ENABLE_I2CGET || ENABLE_I2CSET || ENABLE_I2CDUMP */
140
141#if ENABLE_I2CSET
142static int32_t i2c_smbus_write_byte_data(int file,
143 uint8_t cmd, uint8_t value)
144{
145 union i2c_smbus_data data;
146
147 data.byte = value;
148
149 return i2c_smbus_access(file, I2C_SMBUS_WRITE, cmd,
150 I2C_SMBUS_BYTE_DATA, &data);
151}
152
153static int32_t i2c_smbus_write_word_data(int file, uint8_t cmd, uint16_t value)
154{
155 union i2c_smbus_data data;
156
157 data.word = value;
158
159 return i2c_smbus_access(file, I2C_SMBUS_WRITE, cmd,
160 I2C_SMBUS_WORD_DATA, &data);
161}
162
163static int32_t i2c_smbus_write_block_data(int file, uint8_t cmd,
164 uint8_t length, const uint8_t *values)
165{
166 union i2c_smbus_data data;
167
168 if (length > I2C_SMBUS_BLOCK_MAX)
169 length = I2C_SMBUS_BLOCK_MAX;
170
171 memcpy(data.block+1, values, length);
172 data.block[0] = length;
173
174 return i2c_smbus_access(file, I2C_SMBUS_WRITE, cmd,
175 I2C_SMBUS_BLOCK_DATA, &data);
176}
177
178static int32_t i2c_smbus_write_i2c_block_data(int file, uint8_t cmd,
179 uint8_t length, const uint8_t *values)
180{
181 union i2c_smbus_data data;
182
183 if (length > I2C_SMBUS_BLOCK_MAX)
184 length = I2C_SMBUS_BLOCK_MAX;
185
186 memcpy(data.block+1, values, length);
187 data.block[0] = length;
188
189 return i2c_smbus_access(file, I2C_SMBUS_WRITE, cmd,
190 I2C_SMBUS_I2C_BLOCK_BROKEN, &data);
191}
192#endif /* ENABLE_I2CSET */
193
194#if ENABLE_I2CDUMP
195/*
196 * Returns the number of bytes read, vals must hold at
197 * least I2C_SMBUS_BLOCK_MAX bytes.
198 */
199static int32_t i2c_smbus_read_block_data(int fd, uint8_t cmd, uint8_t *vals)
200{
201 union i2c_smbus_data data;
202 int i, err;
203
204 err = i2c_smbus_access(fd, I2C_SMBUS_READ, cmd,
205 I2C_SMBUS_BLOCK_DATA, &data);
206 if (err < 0)
207 return err;
208
209 for (i = 1; i <= data.block[0]; i++)
210 *vals++ = data.block[i];
211 return data.block[0];
212}
213
214static int32_t i2c_smbus_read_i2c_block_data(int fd, uint8_t cmd,
215 uint8_t len, uint8_t *vals)
216{
217 union i2c_smbus_data data;
218 int i, err;
219
220 if (len > I2C_SMBUS_BLOCK_MAX)
221 len = I2C_SMBUS_BLOCK_MAX;
222 data.block[0] = len;
223
224 err = i2c_smbus_access(fd, I2C_SMBUS_READ, cmd,
225 len == 32 ? I2C_SMBUS_I2C_BLOCK_BROKEN :
226 I2C_SMBUS_I2C_BLOCK_DATA, &data);
227 if (err < 0)
228 return err;
229
230 for (i = 1; i <= data.block[0]; i++)
231 *vals++ = data.block[i];
232 return data.block[0];
233}
234#endif /* ENABLE_I2CDUMP */
235
236#if ENABLE_I2CDETECT
237static int32_t i2c_smbus_write_quick(int fd, uint8_t val)
238{
239 return i2c_smbus_access(fd, val, 0, I2C_SMBUS_QUICK, NULL);
240}
241#endif /* ENABLE_I2CDETECT */
242
243static int i2c_bus_lookup(const char *bus_str)
244{
245 return xstrtou_range(bus_str, 10, 0, 0xfffff);
246}
247
248#if ENABLE_I2CGET || ENABLE_I2CSET || ENABLE_I2CDUMP
249static int i2c_parse_bus_addr(const char *addr_str)
250{
251 /* Slave address must be in range 0x03 - 0x77. */
252 return xstrtou_range(addr_str, 16, 0x03, 0x77);
253}
254
255static void i2c_set_pec(int fd, int pec)
256{
257 ioctl_or_perror_and_die(fd, I2C_PEC,
258 itoptr(pec ? 1 : 0),
259 "can't set PEC");
260}
261
262static void i2c_set_slave_addr(int fd, int addr, int force)
263{
264 ioctl_or_perror_and_die(fd, force ? I2C_SLAVE_FORCE : I2C_SLAVE,
265 itoptr(addr),
266 "can't set address to 0x%02x", addr);
267}
268#endif /* ENABLE_I2CGET || ENABLE_I2CSET || ENABLE_I2CDUMP */
269
270#if ENABLE_I2CGET || ENABLE_I2CSET
271static int i2c_parse_data_addr(const char *data_addr)
272{
273 /* Data address must be an 8 bit integer. */
274 return xstrtou_range(data_addr, 16, 0, 0xff);
275}
276#endif /* ENABLE_I2CGET || ENABLE_I2CSET */
277
278/*
279 * Opens the device file associated with given i2c bus.
280 *
281 * Upstream i2c-tools also support opening devices by i2c bus name
282 * but we drop it here for size reduction.
283 */
284static int i2c_dev_open(int i2cbus)
285{
286 char filename[sizeof("/dev/i2c-%d") + sizeof(int)*3];
287 int fd;
288
289 sprintf(filename, "/dev/i2c-%d", i2cbus);
290 fd = open(filename, O_RDWR);
291 if (fd < 0) {
292 if (errno == ENOENT) {
293 filename[8] = '/'; /* change to "/dev/i2c/%d" */
294 fd = xopen(filename, O_RDWR);
295 } else {
296 bb_perror_msg_and_die("can't open '%s'", filename);
297 }
298 }
299
300 return fd;
301}
302
303/* Size reducing helpers for xxx_check_funcs(). */
304static void get_funcs_matrix(int fd, unsigned long *funcs)
305{
306 ioctl_or_perror_and_die(fd, I2C_FUNCS, funcs,
307 "can't get adapter functionality matrix");
308}
309
310#if ENABLE_I2CGET || ENABLE_I2CSET || ENABLE_I2CDUMP
311static void check_funcs_test_end(int funcs, int pec, const char *err)
312{
313 if (pec && !(funcs & (I2C_FUNC_SMBUS_PEC | I2C_FUNC_I2C)))
314 bb_error_msg("warning: adapter does not support PEC");
315
316 if (err)
317 bb_error_msg_and_die(
318 "adapter has no %s capability", err);
319}
320#endif /* ENABLE_I2CGET || ENABLE_I2CSET || ENABLE_I2CDUMP */
321
322/*
323 * The below functions emit an error message and exit if the adapter doesn't
324 * support desired functionalities.
325 */
326#if ENABLE_I2CGET || ENABLE_I2CDUMP
327static void check_read_funcs(int fd, int mode, int data_addr, int pec)
328{
329 unsigned long funcs;
330 const char *err = NULL;
331
332 get_funcs_matrix(fd, &funcs);
333 switch (mode) {
334 case I2C_SMBUS_BYTE:
335 if (!(funcs & I2C_FUNC_SMBUS_READ_BYTE)) {
336 err = "SMBus receive byte";
337 break;
338 }
339 if (data_addr >= 0 && !(funcs & I2C_FUNC_SMBUS_WRITE_BYTE))
340 err = "SMBus send byte";
341 break;
342 case I2C_SMBUS_BYTE_DATA:
343 if (!(funcs & I2C_FUNC_SMBUS_READ_BYTE_DATA))
344 err = "SMBus read byte";
345 break;
346 case I2C_SMBUS_WORD_DATA:
347 if (!(funcs & I2C_FUNC_SMBUS_READ_WORD_DATA))
348 err = "SMBus read word";
349 break;
350#if ENABLE_I2CDUMP
351 case I2C_SMBUS_BLOCK_DATA:
352 if (!(funcs & I2C_FUNC_SMBUS_READ_BLOCK_DATA))
353 err = "SMBus block read";
354 break;
355
356 case I2C_SMBUS_I2C_BLOCK_DATA:
357 if (!(funcs & I2C_FUNC_SMBUS_READ_I2C_BLOCK))
358 err = "I2C block read";
359 break;
360#endif /* ENABLE_I2CDUMP */
361 default:
362 bb_error_msg_and_die("internal error");
363 }
364 check_funcs_test_end(funcs, pec, err);
365}
366#endif /* ENABLE_I2CGET || ENABLE_I2CDUMP */
367
368#if ENABLE_I2CSET
369static void check_write_funcs(int fd, int mode, int pec)
370{
371 unsigned long funcs;
372 const char *err = NULL;
373
374 get_funcs_matrix(fd, &funcs);
375 switch (mode) {
376 case I2C_SMBUS_BYTE:
377 if (!(funcs & I2C_FUNC_SMBUS_WRITE_BYTE))
378 err = "SMBus send byte";
379 break;
380
381 case I2C_SMBUS_BYTE_DATA:
382 if (!(funcs & I2C_FUNC_SMBUS_WRITE_BYTE_DATA))
383 err = "SMBus write byte";
384 break;
385
386 case I2C_SMBUS_WORD_DATA:
387 if (!(funcs & I2C_FUNC_SMBUS_WRITE_WORD_DATA))
388 err = "SMBus write word";
389 break;
390
391 case I2C_SMBUS_BLOCK_DATA:
392 if (!(funcs & I2C_FUNC_SMBUS_WRITE_BLOCK_DATA))
393 err = "SMBus block write";
394 break;
395 case I2C_SMBUS_I2C_BLOCK_DATA:
396 if (!(funcs & I2C_FUNC_SMBUS_WRITE_I2C_BLOCK))
397 err = "I2C block write";
398 break;
399 }
400 check_funcs_test_end(funcs, pec, err);
401}
402#endif /* ENABLE_I2CSET */
403
404static void confirm_or_abort(void)
405{
406 fprintf(stderr, "Continue? [y/N] ");
407 fflush_all();
408 if (!bb_ask_confirmation())
409 bb_error_msg_and_die("aborting");
410}
411
412/*
413 * Return only if user confirms the action, abort otherwise.
414 *
415 * The messages displayed here are much less elaborate than their i2c-tools
416 * counterparts - this is done for size reduction.
417 */
418static void confirm_action(int bus_addr, int mode, int data_addr, int pec)
419{
420 bb_error_msg("WARNING! This program can confuse your I2C bus");
421
422 /* Don't let the user break his/her EEPROMs */
423 if (bus_addr >= 0x50 && bus_addr <= 0x57 && pec) {
424 bb_error_msg_and_die("this is I2C not smbus - using PEC on I2C "
425 "devices may result in data loss, aborting");
426 }
427
428 if (mode == I2C_SMBUS_BYTE && data_addr >= 0 && pec)
429 bb_error_msg("WARNING! May interpret a write byte command "
430 "with PEC as a write byte data command");
431
432 if (pec)
433 bb_error_msg("PEC checking enabled");
434
435 confirm_or_abort();
436}
437
438#if ENABLE_I2CGET
439//usage:#define i2cget_trivial_usage
440//usage: "[-f] [-y] BUS CHIP-ADDRESS [DATA-ADDRESS [MODE]]"
441//usage:#define i2cget_full_usage "\n\n"
442//usage: "Read from I2C/SMBus chip registers\n"
443//usage: "\n I2CBUS i2c bus number"
444//usage: "\n ADDRESS 0x03 - 0x77"
445//usage: "\nMODE is:"
446//usage: "\n b read byte data (default)"
447//usage: "\n w read word data"
448//usage: "\n c write byte/read byte"
449//usage: "\n Append p for SMBus PEC"
450//usage: "\n"
451//usage: "\n -f force access"
452//usage: "\n -y disable interactive mode"
453int i2cget_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
454int i2cget_main(int argc UNUSED_PARAM, char **argv)
455{
456 const unsigned opt_f = (1 << 0), opt_y = (1 << 1);
457 const char *const optstr = "fy";
458
459 int bus_num, bus_addr, data_addr = -1, status;
460 int mode = I2C_SMBUS_BYTE, pec = 0, fd;
461 unsigned opts;
462
463 opt_complementary = "-2:?4"; /* from 2 to 4 args */
464 opts = getopt32(argv, optstr);
465 argv += optind;
466
467 bus_num = i2c_bus_lookup(argv[0]);
468 bus_addr = i2c_parse_bus_addr(argv[1]);
469
470 if (argv[2]) {
471 data_addr = i2c_parse_data_addr(argv[2]);
472 mode = I2C_SMBUS_BYTE_DATA;
473 if (argv[3]) {
474 switch (argv[3][0]) {
475 case 'b': /* Already set */ break;
476 case 'w': mode = I2C_SMBUS_WORD_DATA; break;
477 case 'c': mode = I2C_SMBUS_BYTE; break;
478 default:
479 bb_error_msg("invalid mode");
480 bb_show_usage();
481 }
482 pec = argv[3][1] == 'p';
483 }
484 }
485
486 fd = i2c_dev_open(bus_num);
487 check_read_funcs(fd, mode, data_addr, pec);
488 i2c_set_slave_addr(fd, bus_addr, opts & opt_f);
489
490 if (!(opts & opt_y))
491 confirm_action(bus_addr, mode, data_addr, pec);
492
493 if (pec)
494 i2c_set_pec(fd, 1);
495
496 switch (mode) {
497 case I2C_SMBUS_BYTE:
498 if (data_addr >= 0) {
499 status = i2c_smbus_write_byte(fd, data_addr);
500 if (status < 0)
501 bb_error_msg("warning - write failed");
502 }
503 status = i2c_smbus_read_byte(fd);
504 break;
505 case I2C_SMBUS_WORD_DATA:
506 status = i2c_smbus_read_word_data(fd, data_addr);
507 break;
508 default: /* I2C_SMBUS_BYTE_DATA */
509 status = i2c_smbus_read_byte_data(fd, data_addr);
510 }
511 close(fd);
512
513 if (status < 0)
514 bb_perror_msg_and_die("read failed");
515
516 printf("0x%0*x\n", mode == I2C_SMBUS_WORD_DATA ? 4 : 2, status);
517
518 return 0;
519}
520#endif /* ENABLE_I2CGET */
521
522#if ENABLE_I2CSET
523//usage:#define i2cset_trivial_usage
524//usage: "[-f] [-y] [-m MASK] BUS CHIP-ADDR DATA-ADDR [VALUE] ... [MODE]"
525//usage:#define i2cset_full_usage "\n\n"
526//usage: "Set I2C registers\n"
527//usage: "\n I2CBUS i2c bus number"
528//usage: "\n ADDRESS 0x03 - 0x77"
529//usage: "\nMODE is:"
530//usage: "\n c byte, no value"
531//usage: "\n b byte data (default)"
532//usage: "\n w word data"
533//usage: "\n i I2C block data"
534//usage: "\n s SMBus block data"
535//usage: "\n Append p for SMBus PEC"
536//usage: "\n"
537//usage: "\n -f force access"
538//usage: "\n -y disable interactive mode"
539//usage: "\n -r read back and compare the result"
540//usage: "\n -m MASK mask specifying which bits to write"
541int i2cset_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
542int i2cset_main(int argc, char **argv)
543{
544 const unsigned opt_f = (1 << 0), opt_y = (1 << 1),
545 opt_m = (1 << 2), opt_r = (1 << 3);
546 const char *const optstr = "fym:r";
547
548 int bus_num, bus_addr, data_addr, mode = I2C_SMBUS_BYTE, pec = 0;
549 int val, blen = 0, mask = 0, fd, status;
550 unsigned char block[I2C_SMBUS_BLOCK_MAX];
551 char *opt_m_arg = NULL;
552 unsigned opts;
553
554 opt_complementary = "-3"; /* from 3 to ? args */
555 opts = getopt32(argv, optstr, &opt_m_arg);
556 argv += optind;
557 argc -= optind;
558
559 bus_num = i2c_bus_lookup(argv[0]);
560 bus_addr = i2c_parse_bus_addr(argv[1]);
561 data_addr = i2c_parse_data_addr(argv[2]);
562
563 if (argv[3]) {
564 if (!argv[4] && argv[3][0] != 'c') {
565 mode = I2C_SMBUS_BYTE_DATA; /* Implicit b */
566 } else {
567 switch (argv[argc-1][0]) {
568 case 'c': /* Already set */ break;
569 case 'b': mode = I2C_SMBUS_BYTE_DATA; break;
570 case 'w': mode = I2C_SMBUS_WORD_DATA; break;
571 case 's': mode = I2C_SMBUS_BLOCK_DATA; break;
572 case 'i': mode = I2C_SMBUS_I2C_BLOCK_DATA; break;
573 default:
574 bb_error_msg("invalid mode");
575 bb_show_usage();
576 }
577
578 pec = argv[argc-1][1] == 'p';
579 if (mode == I2C_SMBUS_BLOCK_DATA ||
580 mode == I2C_SMBUS_I2C_BLOCK_DATA) {
581 if (pec && mode == I2C_SMBUS_I2C_BLOCK_DATA)
582 bb_error_msg_and_die(
583 "PEC not supported for I2C "
584 "block writes");
585 if (opts & opt_m)
586 bb_error_msg_and_die(
587 "mask not supported for block "
588 "writes");
589 }
590 }
591 }
592
593 /* Prepare the value(s) to be written according to current mode. */
594 switch (mode) {
595 case I2C_SMBUS_BYTE_DATA:
596 val = xstrtou_range(argv[3], 0, 0, 0xff);
597 break;
598 case I2C_SMBUS_WORD_DATA:
599 val = xstrtou_range(argv[3], 0, 0, 0xffff);
600 break;
601 case I2C_SMBUS_BLOCK_DATA:
602 case I2C_SMBUS_I2C_BLOCK_DATA:
603 for (blen = 3; blen < (argc - 1); blen++)
604 block[blen] = xstrtou_range(argv[blen], 0, 0, 0xff);
605 val = -1;
606 break;
607 default:
608 val = -1;
609 break;
610 }
611
612 if (opts & opt_m) {
613 mask = xstrtou_range(opt_m_arg, 0, 0,
614 (mode == I2C_SMBUS_BYTE ||
615 mode == I2C_SMBUS_BYTE_DATA) ? 0xff : 0xffff);
616 }
617
618 fd = i2c_dev_open(bus_num);
619 check_write_funcs(fd, mode, pec);
620 i2c_set_slave_addr(fd, bus_addr, opts & opt_f);
621
622 if (!(opts & opt_y))
623 confirm_action(bus_addr, mode, data_addr, pec);
624
625 /*
626 * If we're using mask - read the current value here and adjust the
627 * value to be written.
628 */
629 if (opts & opt_m) {
630 int tmpval;
631
632 switch (mode) {
633 case I2C_SMBUS_BYTE:
634 tmpval = i2c_smbus_read_byte(fd);
635 break;
636 case I2C_SMBUS_WORD_DATA:
637 tmpval = i2c_smbus_read_word_data(fd, data_addr);
638 break;
639 default:
640 tmpval = i2c_smbus_read_byte_data(fd, data_addr);
641 }
642
643 if (tmpval < 0)
644 bb_perror_msg_and_die("can't read old value");
645
646 val = (val & mask) | (tmpval & ~mask);
647
648 if (!(opts & opt_y)) {
649 bb_error_msg("old value 0x%0*x, write mask "
650 "0x%0*x, will write 0x%0*x to register "
651 "0x%02x",
652 mode == I2C_SMBUS_WORD_DATA ? 4 : 2, tmpval,
653 mode == I2C_SMBUS_WORD_DATA ? 4 : 2, mask,
654 mode == I2C_SMBUS_WORD_DATA ? 4 : 2, val,
655 data_addr);
656 confirm_or_abort();
657 }
658 }
659
660 if (pec)
661 i2c_set_pec(fd, 1);
662
663 switch (mode) {
664 case I2C_SMBUS_BYTE:
665 status = i2c_smbus_write_byte(fd, data_addr);
666 break;
667 case I2C_SMBUS_WORD_DATA:
668 status = i2c_smbus_write_word_data(fd, data_addr, val);
669 break;
670 case I2C_SMBUS_BLOCK_DATA:
671 status = i2c_smbus_write_block_data(fd, data_addr,
672 blen, block);
673 break;
674 case I2C_SMBUS_I2C_BLOCK_DATA:
675 status = i2c_smbus_write_i2c_block_data(fd, data_addr,
676 blen, block);
677 break;
678 default: /* I2C_SMBUS_BYTE_DATA */
679 status = i2c_smbus_write_byte_data(fd, data_addr, val);
680 break;
681 }
682 if (status < 0)
683 bb_perror_msg_and_die("write failed");
684
685 if (pec)
686 i2c_set_pec(fd, 0); /* Clear PEC. */
687
688 /* No readback required - we're done. */
689 if (!(opts & opt_r))
690 return 0;
691
692 switch (mode) {
693 case I2C_SMBUS_BYTE:
694 status = i2c_smbus_read_byte(fd);
695 val = data_addr;
696 break;
697 case I2C_SMBUS_WORD_DATA:
698 status = i2c_smbus_read_word_data(fd, data_addr);
699 break;
700 default: /* I2C_SMBUS_BYTE_DATA */
701 status = i2c_smbus_read_byte_data(fd, data_addr);
702 }
703
704 if (status < 0) {
705 puts("Warning - readback failed");
706 } else
707 if (status != val) {
708 printf("Warning - data mismatch - wrote "
709 "0x%0*x, read back 0x%0*x\n",
710 mode == I2C_SMBUS_WORD_DATA ? 4 : 2, val,
711 mode == I2C_SMBUS_WORD_DATA ? 4 : 2, status);
712 } else {
713 printf("Value 0x%0*x written, readback matched\n",
714 mode == I2C_SMBUS_WORD_DATA ? 4 : 2, val);
715 }
716
717 return 0;
718}
719#endif /* ENABLE_I2CSET */
720
721#if ENABLE_I2CDUMP
722static int read_block_data(int buf_fd, int mode, int *block)
723{
724 uint8_t cblock[I2C_SMBUS_BLOCK_MAX + I2CDUMP_NUM_REGS];
725 int res, blen = 0, tmp, i;
726
727 if (mode == I2C_SMBUS_BLOCK_DATA) {
728 blen = i2c_smbus_read_block_data(buf_fd, 0, cblock);
729 if (blen <= 0)
730 goto fail;
731 } else {
732 for (res = 0; res < I2CDUMP_NUM_REGS; res += tmp) {
733 tmp = i2c_smbus_read_i2c_block_data(
734 buf_fd, res, I2C_SMBUS_BLOCK_MAX,
735 cblock + res);
736 if (tmp <= 0) {
737 blen = tmp;
738 goto fail;
739 }
740 }
741
742 if (res >= I2CDUMP_NUM_REGS)
743 res = I2CDUMP_NUM_REGS;
744
745 for (i = 0; i < res; i++)
746 block[i] = cblock[i];
747
748 if (mode != I2C_SMBUS_BLOCK_DATA)
749 for (i = res; i < I2CDUMP_NUM_REGS; i++)
750 block[i] = -1;
751 }
752
753 return blen;
754
755 fail:
756 bb_error_msg_and_die("block read failed: %d", blen);
757}
758
759/* Dump all but word data. */
760static void dump_data(int bus_fd, int mode, unsigned first,
761 unsigned last, int *block, int blen)
762{
763 int i, j, res;
764
765 puts(" 0 1 2 3 4 5 6 7 8 9 a b c d e f"
766 " 0123456789abcdef");
767
768 for (i = 0; i < I2CDUMP_NUM_REGS; i += 0x10) {
769 if (mode == I2C_SMBUS_BLOCK_DATA && i >= blen)
770 break;
771 if (i/16 < first/16)
772 continue;
773 if (i/16 > last/16)
774 break;
775
776 printf("%02x: ", i);
777 for (j = 0; j < 16; j++) {
778 fflush_all();
779 /* Skip unwanted registers */
780 if (i+j < first || i+j > last) {
781 printf(" ");
782 if (mode == I2C_SMBUS_WORD_DATA) {
783 printf(" ");
784 j++;
785 }
786 continue;
787 }
788
789 switch (mode) {
790 case I2C_SMBUS_BYTE_DATA:
791 res = i2c_smbus_read_byte_data(bus_fd, i+j);
792 block[i+j] = res;
793 break;
794 case I2C_SMBUS_WORD_DATA:
795 res = i2c_smbus_read_word_data(bus_fd, i+j);
796 if (res < 0) {
797 block[i+j] = res;
798 block[i+j+1] = res;
799 } else {
800 block[i+j] = res & 0xff;
801 block[i+j+1] = res >> 8;
802 }
803 break;
804 case I2C_SMBUS_BYTE:
805 res = i2c_smbus_read_byte(bus_fd);
806 block[i+j] = res;
807 break;
808 default:
809 res = block[i+j];
810 }
811
812 if (mode == I2C_SMBUS_BLOCK_DATA &&
813 i+j >= blen) {
814 printf(" ");
815 } else if (res < 0) {
816 printf("XX ");
817 if (mode == I2C_SMBUS_WORD_DATA)
818 printf("XX ");
819 } else {
820 printf("%02x ", block[i+j]);
821 if (mode == I2C_SMBUS_WORD_DATA)
822 printf("%02x ", block[i+j+1]);
823 }
824
825 if (mode == I2C_SMBUS_WORD_DATA)
826 j++;
827 }
828 printf(" ");
829
830 for (j = 0; j < 16; j++) {
831 if (mode == I2C_SMBUS_BLOCK_DATA && i+j >= blen)
832 break;
833 /* Skip unwanted registers */
834 if (i+j < first || i+j > last) {
835 bb_putchar(' ');
836 continue;
837 }
838
839 res = block[i+j];
840 if (res < 0) {
841 bb_putchar('X');
842 } else if (res == 0x00 || res == 0xff) {
843 bb_putchar('.');
844 } else if (res < 32 || res >= 127) {
845 bb_putchar('?');
846 } else {
847 bb_putchar(res);
848 }
849 }
850 bb_putchar('\n');
851 }
852}
853
854static void dump_word_data(int bus_fd, unsigned first, unsigned last)
855{
856 int i, j, rv;
857
858 /* Word data. */
859 puts(" 0,8 1,9 2,a 3,b 4,c 5,d 6,e 7,f");
860 for (i = 0; i < 256; i += 8) {
861 if (i/8 < first/8)
862 continue;
863 if (i/8 > last/8)
864 break;
865
866 printf("%02x: ", i);
867 for (j = 0; j < 8; j++) {
868 /* Skip unwanted registers. */
869 if (i+j < first || i+j > last) {
870 printf(" ");
871 continue;
872 }
873
874 rv = i2c_smbus_read_word_data(bus_fd, i+j);
875 if (rv < 0)
876 printf("XXXX ");
877 else
878 printf("%04x ", rv & 0xffff);
879 }
880 bb_putchar('\n');
881 }
882}
883
884//usage:#define i2cdump_trivial_usage
885//usage: "[-f] [-r FIRST-LAST] [-y] BUS ADDR [MODE]"
886//usage:#define i2cdump_full_usage "\n\n"
887//usage: "Examine I2C registers\n"
888//usage: "\n I2CBUS i2c bus number"
889//usage: "\n ADDRESS 0x03 - 0x77"
890//usage: "\nMODE is:"
891//usage: "\n b byte (default)"
892//usage: "\n w word"
893//usage: "\n W word on even register addresses"
894//usage: "\n i I2C block"
895//usage: "\n s SMBus block"
896//usage: "\n c consecutive byte"
897//usage: "\n Append p for SMBus PEC"
898//usage: "\n"
899//usage: "\n -f force access"
900//usage: "\n -y disable interactive mode"
901//usage: "\n -r limit the number of registers being accessed"
902int i2cdump_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
903int i2cdump_main(int argc UNUSED_PARAM, char **argv)
904{
905 const unsigned opt_f = (1 << 0), opt_y = (1 << 1),
906 opt_r = (1 << 2);
907 const char *const optstr = "fyr:";
908
909 int bus_num, bus_addr, mode = I2C_SMBUS_BYTE_DATA, even = 0, pec = 0;
910 unsigned first = 0x00, last = 0xff, opts;
911 int *block = (int *)bb_common_bufsiz1;
912 char *opt_r_str, *dash;
913 int fd, res;
914
915 opt_complementary = "-2:?3"; /* from 2 to 3 args */
916 opts = getopt32(argv, optstr, &opt_r_str);
917 argv += optind;
918
919 bus_num = i2c_bus_lookup(argv[0]);
920 bus_addr = i2c_parse_bus_addr(argv[1]);
921
922 if (argv[2]) {
923 switch (argv[2][0]) {
924 case 'b': /* Already set. */ break;
925 case 'c': mode = I2C_SMBUS_BYTE; break;
926 case 'w': mode = I2C_SMBUS_WORD_DATA; break;
927 case 'W':
928 mode = I2C_SMBUS_WORD_DATA;
929 even = 1;
930 break;
931 case 's': mode = I2C_SMBUS_BLOCK_DATA; break;
932 case 'i': mode = I2C_SMBUS_I2C_BLOCK_DATA; break;
933 default:
934 bb_error_msg_and_die("invalid mode");
935 }
936
937 if (argv[2][1] == 'p') {
938 if (argv[2][0] == 'W' || argv[2][0] == 'i') {
939 bb_error_msg_and_die(
940 "pec not supported for -W and -i");
941 } else {
942 pec = 1;
943 }
944 }
945 }
946
947 if (opts & opt_r) {
948 first = strtol(opt_r_str, &dash, 0);
949 if (dash == opt_r_str || *dash != '-' || first > 0xff)
950 bb_error_msg_and_die("invalid range");
951 last = xstrtou_range(++dash, 0, first, 0xff);
952
953 /* Range is not available for every mode. */
954 switch (mode) {
955 case I2C_SMBUS_BYTE:
956 case I2C_SMBUS_BYTE_DATA:
957 break;
958 case I2C_SMBUS_WORD_DATA:
959 if (!even || (!(first % 2) && last % 2))
960 break;
961 /* Fall through */
962 default:
963 bb_error_msg_and_die(
964 "range not compatible with selected mode");
965 }
966 }
967
968 fd = i2c_dev_open(bus_num);
969 check_read_funcs(fd, mode, -1 /* data_addr */, pec);
970 i2c_set_slave_addr(fd, bus_addr, opts & opt_f);
971
972 if (pec)
973 i2c_set_pec(fd, 1);
974
975 if (!(opts & opt_y))
976 confirm_action(bus_addr, mode, -1 /* data_addr */, pec);
977
978 /* All but word data. */
979 if (mode != I2C_SMBUS_WORD_DATA || even) {
980 int blen = 0;
981
982 if (mode == I2C_SMBUS_BLOCK_DATA || mode == I2C_SMBUS_I2C_BLOCK_DATA)
983 blen = read_block_data(fd, mode, block);
984
985 if (mode == I2C_SMBUS_BYTE) {
986 res = i2c_smbus_write_byte(fd, first);
987 if (res < 0)
988 bb_perror_msg_and_die("write start address");
989 }
990
991 dump_data(fd, mode, first, last, block, blen);
992 } else {
993 dump_word_data(fd, first, last);
994 }
995
996 return 0;
997}
998#endif /* ENABLE_I2CDUMP */
999
1000#if ENABLE_I2CDETECT
1001enum adapter_type {
1002 ADT_DUMMY = 0,
1003 ADT_ISA,
1004 ADT_I2C,
1005 ADT_SMBUS,
1006};
1007
1008struct adap_desc {
1009 const char *funcs;
1010 const char *algo;
1011};
1012
1013static const struct adap_desc adap_descs[] = {
1014 { .funcs = "dummy",
1015 .algo = "Dummy bus", },
1016 { .funcs = "isa",
1017 .algo = "ISA bus", },
1018 { .funcs = "i2c",
1019 .algo = "I2C adapter", },
1020 { .funcs = "smbus",
1021 .algo = "SMBus adapter", },
1022};
1023
1024struct i2c_func
1025{
1026 long value;
1027 const char* name;
1028};
1029
1030static const struct i2c_func i2c_funcs_tab[] = {
1031 { .value = I2C_FUNC_I2C,
1032 .name = "I2C" },
1033 { .value = I2C_FUNC_SMBUS_QUICK,
1034 .name = "SMBus quick command" },
1035 { .value = I2C_FUNC_SMBUS_WRITE_BYTE,
1036 .name = "SMBus send byte" },
1037 { .value = I2C_FUNC_SMBUS_READ_BYTE,
1038 .name = "SMBus receive byte" },
1039 { .value = I2C_FUNC_SMBUS_WRITE_BYTE_DATA,
1040 .name = "SMBus write byte" },
1041 { .value = I2C_FUNC_SMBUS_READ_BYTE_DATA,
1042 .name = "SMBus read byte" },
1043 { .value = I2C_FUNC_SMBUS_WRITE_WORD_DATA,
1044 .name = "SMBus write word" },
1045 { .value = I2C_FUNC_SMBUS_READ_WORD_DATA,
1046 .name = "SMBus read word" },
1047 { .value = I2C_FUNC_SMBUS_PROC_CALL,
1048 .name = "SMBus process call" },
1049 { .value = I2C_FUNC_SMBUS_WRITE_BLOCK_DATA,
1050 .name = "SMBus block write" },
1051 { .value = I2C_FUNC_SMBUS_READ_BLOCK_DATA,
1052 .name = "SMBus block read" },
1053 { .value = I2C_FUNC_SMBUS_BLOCK_PROC_CALL,
1054 .name = "SMBus block process call" },
1055 { .value = I2C_FUNC_SMBUS_PEC,
1056 .name = "SMBus PEC" },
1057 { .value = I2C_FUNC_SMBUS_WRITE_I2C_BLOCK,
1058 .name = "I2C block write" },
1059 { .value = I2C_FUNC_SMBUS_READ_I2C_BLOCK,
1060 .name = "I2C block read" },
1061 { .value = 0, .name = NULL }
1062};
1063
1064static enum adapter_type i2cdetect_get_funcs(int bus)
1065{
1066 enum adapter_type ret;
1067 unsigned long funcs;
1068 int fd;
1069
1070 fd = i2c_dev_open(bus);
1071
1072 get_funcs_matrix(fd, &funcs);
1073 if (funcs & I2C_FUNC_I2C)
1074 ret = ADT_I2C;
1075 else if (funcs & (I2C_FUNC_SMBUS_BYTE |
1076 I2C_FUNC_SMBUS_BYTE_DATA |
1077 I2C_FUNC_SMBUS_WORD_DATA))
1078 ret = ADT_SMBUS;
1079 else
1080 ret = ADT_DUMMY;
1081
1082 close(fd);
1083
1084 return ret;
1085}
1086
1087static void NORETURN list_i2c_busses_and_exit(void)
1088{
1089 const char *const i2cdev_path = "/sys/class/i2c-dev";
1090
1091 char path[NAME_MAX], name[128];
1092 struct dirent *de, *subde;
1093 enum adapter_type adt;
1094 DIR *dir, *subdir;
1095 int rv, bus;
1096 char *pos;
1097 FILE *fp;
1098
1099 /*
1100 * XXX Upstream i2cdetect also looks for i2c bus info in /proc/bus/i2c,
1101 * but we won't bother since it's only useful on older kernels (before
1102 * 2.6.5). We expect sysfs to be present and mounted at /sys/.
1103 */
1104
1105 dir = xopendir(i2cdev_path);
1106 while ((de = readdir(dir))) {
1107 if (de->d_name[0] == '.')
1108 continue;
1109
1110 /* Simple version for ISA chips. */
1111 snprintf(path, NAME_MAX, "%s/%s/name",
1112 i2cdev_path, de->d_name);
1113 fp = fopen(path, "r");
1114 if (fp == NULL) {
1115 snprintf(path, NAME_MAX,
1116 "%s/%s/device/name",
1117 i2cdev_path, de->d_name);
1118 fp = fopen(path, "r");
1119 }
1120
1121 /* Non-ISA chips require the hard-way. */
1122 if (fp == NULL) {
1123 snprintf(path, NAME_MAX,
1124 "%s/%s/device/name",
1125 i2cdev_path, de->d_name);
1126 subdir = opendir(path);
1127 if (subdir == NULL)
1128 continue;
1129
1130 while ((subde = readdir(subdir))) {
1131 if (subde->d_name[0] == '.')
1132 continue;
1133
1134 if (is_prefixed_with(subde->d_name, "i2c-")) {
1135 snprintf(path, NAME_MAX,
1136 "%s/%s/device/%s/name",
1137 i2cdev_path, de->d_name,
1138 subde->d_name);
1139 fp = fopen(path, "r");
1140 break;
1141 }
1142 }
1143 }
1144
1145 if (fp != NULL) {
1146 /*
1147 * Get the rest of the info and display a line
1148 * for a single bus.
1149 */
1150 memset(name, 0, sizeof(name));
1151 pos = fgets(name, sizeof(name), fp);
1152 fclose(fp);
1153 if (pos == NULL)
1154 continue;
1155
1156 pos = strchr(name, '\n');
1157 if (pos != NULL)
1158 *pos = '\0';
1159
1160 rv = sscanf(de->d_name, "i2c-%d", &bus);
1161 if (rv != 1)
1162 continue;
1163
1164 if (is_prefixed_with(name, "ISA"))
1165 adt = ADT_ISA;
1166 else
1167 adt = i2cdetect_get_funcs(bus);
1168
1169 printf(
1170 "i2c-%d\t%-10s\t%-32s\t%s\n",
1171 bus, adap_descs[adt].funcs,
1172 name, adap_descs[adt].algo);
1173 }
1174 }
1175
1176 exit(EXIT_SUCCESS);
1177}
1178
1179static void NORETURN no_support(const char *cmd)
1180{
1181 bb_error_msg_and_die("bus doesn't support %s", cmd);
1182}
1183
1184static void will_skip(const char *cmd)
1185{
1186 bb_error_msg(
1187 "warning: can't use %s command, "
1188 "will skip some addresses", cmd);
1189}
1190
1191//usage:#define i2cdetect_trivial_usage
1192//usage: "[-F I2CBUS] [-l] [-y] [-a] [-q|-r] I2CBUS [FIRST LAST]"
1193//usage:#define i2cdetect_full_usage "\n\n"
1194//usage: "Detect I2C chips.\n"
1195//usage: "\n I2CBUS i2c bus number"
1196//usage: "\n FIRST and LAST limit the probing range"
1197//usage: "\n"
1198//usage: "\n -l output list of installed busses"
1199//usage: "\n -y disable interactive mode"
1200//usage: "\n -a force scanning of non-regular addresses"
1201//usage: "\n -q use smbus quick write commands for probing (default)"
1202//usage: "\n -r use smbus read byte commands for probing"
1203//usage: "\n -F display list of functionalities"
1204int i2cdetect_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1205int i2cdetect_main(int argc UNUSED_PARAM, char **argv)
1206{
1207 const unsigned opt_y = (1 << 0), opt_a = (1 << 1),
1208 opt_q = (1 << 2), opt_r = (1 << 3),
1209 opt_F = (1 << 4), opt_l = (1 << 5);
1210 const char *const optstr = "yaqrFl";
1211
1212 int fd, bus_num, i, j, mode = I2CDETECT_MODE_AUTO, status, cmd;
1213 unsigned first = 0x03, last = 0x77, opts;
1214 unsigned long funcs;
1215
1216 opt_complementary = "q--r:r--q:" /* mutually exclusive */
1217 "?3"; /* up to 3 args */
1218 opts = getopt32(argv, optstr);
1219 argv += optind;
1220
1221 if (opts & opt_l)
1222 list_i2c_busses_and_exit();
1223
1224 if (!argv[0])
1225 bb_show_usage();
1226
1227 bus_num = i2c_bus_lookup(argv[0]);
1228 fd = i2c_dev_open(bus_num);
1229 get_funcs_matrix(fd, &funcs);
1230
1231 if (opts & opt_F) {
1232 /* Only list the functionalities. */
1233 printf("Functionalities implemented by bus #%d\n", bus_num);
1234 for (i = 0; i2c_funcs_tab[i].value; i++) {
1235 printf("%-32s %s\n", i2c_funcs_tab[i].name,
1236 funcs & i2c_funcs_tab[i].value ? "yes" : "no");
1237 }
1238
1239 return EXIT_SUCCESS;
1240 }
1241
1242 if (opts & opt_r)
1243 mode = I2CDETECT_MODE_READ;
1244 else if (opts & opt_q)
1245 mode = I2CDETECT_MODE_QUICK;
1246
1247 if (opts & opt_a) {
1248 first = 0x00;
1249 last = 0x7f;
1250 }
1251
1252 /* Read address range. */
1253 if (argv[1]) {
1254 first = xstrtou_range(argv[1], 16, first, last);
1255 if (argv[2])
1256 last = xstrtou_range(argv[2], 16, first, last);
1257 }
1258
1259 if (!(funcs & (I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_READ_BYTE))) {
1260 no_support("detection commands");
1261 } else
1262 if (mode == I2CDETECT_MODE_QUICK && !(funcs & I2C_FUNC_SMBUS_QUICK)) {
1263 no_support("SMBus quick write");
1264 } else
1265 if (mode == I2CDETECT_MODE_READ && !(funcs & I2C_FUNC_SMBUS_READ_BYTE)) {
1266 no_support("SMBus receive byte");
1267 }
1268
1269 if (mode == I2CDETECT_MODE_AUTO) {
1270 if (!(funcs & I2C_FUNC_SMBUS_QUICK))
1271 will_skip("SMBus quick write");
1272 if (!(funcs & I2C_FUNC_SMBUS_READ_BYTE))
1273 will_skip("SMBus receive byte");
1274 }
1275
1276 if (!(opts & opt_y))
1277 confirm_action(-1, -1, -1, 0);
1278
1279 puts(" 0 1 2 3 4 5 6 7 8 9 a b c d e f");
1280 for (i = 0; i < 128; i += 16) {
1281 printf("%02x: ", i);
1282 for (j = 0; j < 16; j++) {
1283 fflush_all();
1284
1285 cmd = mode;
1286 if (mode == I2CDETECT_MODE_AUTO) {
1287 if ((i+j >= 0x30 && i+j <= 0x37) ||
1288 (i+j >= 0x50 && i+j <= 0x5F))
1289 cmd = I2CDETECT_MODE_READ;
1290 else
1291 cmd = I2CDETECT_MODE_QUICK;
1292 }
1293
1294 /* Skip unwanted addresses. */
1295 if (i+j < first
1296 || i+j > last
1297 || (cmd == I2CDETECT_MODE_READ && !(funcs & I2C_FUNC_SMBUS_READ_BYTE))
1298 || (cmd == I2CDETECT_MODE_QUICK && !(funcs & I2C_FUNC_SMBUS_QUICK)))
1299 {
1300 printf(" ");
1301 continue;
1302 }
1303
1304 status = ioctl(fd, I2C_SLAVE, itoptr(i + j));
1305 if (status < 0) {
1306 if (errno == EBUSY) {
1307 printf("UU ");
1308 continue;
1309 }
1310
1311 bb_perror_msg_and_die(
1312 "can't set address to 0x%02x", i + j);
1313 }
1314
1315 switch (cmd) {
1316 case I2CDETECT_MODE_READ:
1317 /*
1318 * This is known to lock SMBus on various
1319 * write-only chips (mainly clock chips).
1320 */
1321 status = i2c_smbus_read_byte(fd);
1322 break;
1323 default: /* I2CDETECT_MODE_QUICK: */
1324 /*
1325 * This is known to corrupt the Atmel
1326 * AT24RF08 EEPROM.
1327 */
1328 status = i2c_smbus_write_quick(fd,
1329 I2C_SMBUS_WRITE);
1330 break;
1331 }
1332
1333 if (status < 0)
1334 printf("-- ");
1335 else
1336 printf("%02x ", i+j);
1337 }
1338 bb_putchar('\n');
1339 }
1340
1341 return 0;
1342}
1343#endif /* ENABLE_I2CDETECT */
Note: See TracBrowser for help on using the repository browser.