vi: fixes to string search in colon commands, closes 10321
[oweals/busybox.git] / miscutils / i2c_tools.c
index 7034dc9a852a4e4fa87797459f754d09412bacda..82a559f74a2be2426ebfea1a506d8aaa09bc0a11 100644 (file)
@@ -8,45 +8,54 @@
  *
  * Licensed under GPLv2 or later, see file LICENSE in this source tree.
  */
-
 //config:config I2CGET
-//config:      bool "i2cget"
+//config:      bool "i2cget (5.5 kb)"
 //config:      default y
 //config:      select PLATFORM_LINUX
 //config:      help
-//config:        Read from I2C/SMBus chip registers.
+//config:      Read from I2C/SMBus chip registers.
 //config:
 //config:config I2CSET
-//config:      bool "i2cset"
+//config:      bool "i2cset (6.7 kb)"
 //config:      default y
 //config:      select PLATFORM_LINUX
 //config:      help
-//config:        Set I2C registers.
+//config:      Set I2C registers.
 //config:
 //config:config I2CDUMP
-//config:      bool "i2cdump"
+//config:      bool "i2cdump (7.1 kb)"
 //config:      default y
 //config:      select PLATFORM_LINUX
 //config:      help
-//config:        Examine I2C registers.
+//config:      Examine I2C registers.
 //config:
 //config:config I2CDETECT
-//config:      bool "i2cdetect"
+//config:      bool "i2cdetect (7.1 kb)"
 //config:      default y
 //config:      select PLATFORM_LINUX
 //config:      help
-//config:        Detect I2C chips.
+//config:      Detect I2C chips.
+//config:
+//config:config I2CTRANSFER
+//config:      bool "i2ctransfer (4.0 kb)"
+//config:      default y
+//config:      select PLATFORM_LINUX
+//config:      help
+//config:      Send user-defined I2C messages in one transfer.
 //config:
 
 //applet:IF_I2CGET(APPLET(i2cget, BB_DIR_USR_SBIN, BB_SUID_DROP))
 //applet:IF_I2CSET(APPLET(i2cset, BB_DIR_USR_SBIN, BB_SUID_DROP))
 //applet:IF_I2CDUMP(APPLET(i2cdump, BB_DIR_USR_SBIN, BB_SUID_DROP))
 //applet:IF_I2CDETECT(APPLET(i2cdetect, BB_DIR_USR_SBIN, BB_SUID_DROP))
+//applet:IF_I2CTRANSFER(APPLET(i2ctransfer, BB_DIR_USR_SBIN, BB_SUID_DROP))
+/* not NOEXEC: if hw operation stalls, use less memory in "hung" process */
 
 //kbuild:lib-$(CONFIG_I2CGET) += i2c_tools.o
 //kbuild:lib-$(CONFIG_I2CSET) += i2c_tools.o
 //kbuild:lib-$(CONFIG_I2CDUMP) += i2c_tools.o
 //kbuild:lib-$(CONFIG_I2CDETECT) += i2c_tools.o
+//kbuild:lib-$(CONFIG_I2CTRANSFER) += i2c_tools.o
 
 /*
  * Unsupported stuff:
 
 #include "libbb.h"
 
-/*
- * /dev/i2c-X ioctl commands. The ioctl's parameter is always an unsigned long,
- * except for:
- *    - I2C_FUNCS, takes pointer to an unsigned long
- *    - I2C_RDWR, takes pointer to struct i2c_rdwr_ioctl_data
- *    - I2C_SMBUS, takes pointer to struct i2c_smbus_ioctl_data
- */
+#include <linux/i2c.h>
 
-/*
- * NOTE: Slave address is 7 or 10 bits, but 10-bit addresses
- * are not supported due to code brokenness.
- */
+#define I2CDUMP_NUM_REGS               256
 
-/* Use this slave address. */
-#define I2C_SLAVE      0x0703
-/* Use this slave address, even if it is already in use by a driver. */
-#define I2C_SLAVE_FORCE        0x0706
-/* 0 for 7 bit addrs, != 0 for 10 bit. */
-#define I2C_TENBIT     0x0704
-/* Get the adapter functionality mask. */
-#define I2C_FUNCS      0x0705
-/* Combined R/W transfer (one STOP only). */
-#define I2C_RDWR       0x0707
-/* != 0 to use PEC with SMBus. */
-#define I2C_PEC                0x0708
-/* SMBus transfer. */
-#define I2C_SMBUS      0x0720
-
-/* Structure used in the I2C_SMBUS ioctl call. */
+#define I2CDETECT_MODE_AUTO            0
+#define I2CDETECT_MODE_QUICK           1
+#define I2CDETECT_MODE_READ            2
+
+/* linux/i2c-dev.h from i2c-tools overwrites the one from linux uapi
+ * and defines symbols already defined by linux/i2c.h.
+ * Also, it defines a bunch of static inlines which we would rather NOT
+ * inline. What a mess.
+ * We need only these definitions from linux/i2c-dev.h:
+ */
+#define I2C_SLAVE                      0x0703
+#define I2C_SLAVE_FORCE                        0x0706
+#define I2C_FUNCS                      0x0705
+#define I2C_PEC                                0x0708
+#define I2C_SMBUS                      0x0720
+#define I2C_RDWR                       0x0707
+#define I2C_RDWR_IOCTL_MAX_MSGS                42
+#define I2C_RDWR_IOCTL_MAX_MSGS_STR    "42"
 struct i2c_smbus_ioctl_data {
-       uint8_t read_write;
-       uint8_t command;
-       uint32_t size;
+       __u8 read_write;
+       __u8 command;
+       __u32 size;
        union i2c_smbus_data *data;
 };
-
-/* Structure used in the I2C_RDWR ioctl call. */
 struct i2c_rdwr_ioctl_data {
-       struct i2c_msg *msgs;   /* Pointers to i2c_msgs. */
-       uint32_t nmsgs;         /* Number of i2c_msgs. */
-};
-
-/* As specified in SMBus standard. */
-#define I2C_SMBUS_BLOCK_MAX            32
-/* Not specified but we use same structure. */
-#define I2C_SMBUS_I2C_BLOCK_MAX                32
-
-/* Data for SMBus Messages. */
-union i2c_smbus_data {
-       uint8_t byte;
-       uint16_t word;
-       /* block[0] is used for length and one more for PEC */
-       uint8_t block[I2C_SMBUS_BLOCK_MAX + 2];
+       struct i2c_msg *msgs;   /* pointers to i2c_msgs */
+       __u32 nmsgs;            /* number of i2c_msgs */
 };
-
-#define I2C_RDRW_IOCTL_MAX_MSGS                42
-#define I2C_MAX_REGS                   256
-
-/* Smbus_access read or write markers. */
-#define I2C_SMBUS_READ                 1
-#define I2C_SMBUS_WRITE                        0
-
-/* SMBus transaction types (size parameter in the below functions). */
-#define I2C_SMBUS_QUICK                        0
-#define I2C_SMBUS_BYTE                 1
-#define I2C_SMBUS_BYTE_DATA            2
-#define I2C_SMBUS_WORD_DATA            3
-#define I2C_SMBUS_PROC_CALL            4
-#define I2C_SMBUS_BLOCK_DATA           5
-#define I2C_SMBUS_I2C_BLOCK_BROKEN     6
-#define I2C_SMBUS_BLOCK_PROC_CALL      7
-#define I2C_SMBUS_I2C_BLOCK_DATA       8
-
-#define DETECT_MODE_AUTO               0
-#define DETECT_MODE_QUICK              1
-#define DETECT_MODE_READ               2
-
-/* Defines to determine what functionality is present. */
-#define I2C_FUNC_I2C                           0x00000001
-#define I2C_FUNC_10BIT_ADDR                    0x00000002
-#define I2C_FUNC_PROTOCOL_MANGLING             0x00000004
-#define I2C_FUNC_SMBUS_PEC                     0x00000008
-#define I2C_FUNC_SMBUS_BLOCK_PROC_CALL         0x00008000
-#define I2C_FUNC_SMBUS_QUICK                   0x00010000
-#define I2C_FUNC_SMBUS_READ_BYTE               0x00020000
-#define I2C_FUNC_SMBUS_WRITE_BYTE              0x00040000
-#define I2C_FUNC_SMBUS_READ_BYTE_DATA          0x00080000
-#define I2C_FUNC_SMBUS_WRITE_BYTE_DATA         0x00100000
-#define I2C_FUNC_SMBUS_READ_WORD_DATA          0x00200000
-#define I2C_FUNC_SMBUS_WRITE_WORD_DATA         0x00400000
-#define I2C_FUNC_SMBUS_PROC_CALL               0x00800000
-#define I2C_FUNC_SMBUS_READ_BLOCK_DATA         0x01000000
-#define I2C_FUNC_SMBUS_WRITE_BLOCK_DATA        0x02000000
-#define I2C_FUNC_SMBUS_READ_I2C_BLOCK          0x04000000
-#define I2C_FUNC_SMBUS_WRITE_I2C_BLOCK         0x08000000
-
-#define I2C_FUNC_SMBUS_BYTE            (I2C_FUNC_SMBUS_READ_BYTE | \
-                                        I2C_FUNC_SMBUS_WRITE_BYTE)
-#define I2C_FUNC_SMBUS_BYTE_DATA       (I2C_FUNC_SMBUS_READ_BYTE_DATA | \
-                                        I2C_FUNC_SMBUS_WRITE_BYTE_DATA)
-#define I2C_FUNC_SMBUS_WORD_DATA       (I2C_FUNC_SMBUS_READ_WORD_DATA | \
-                                        I2C_FUNC_SMBUS_WRITE_WORD_DATA)
-#define I2C_FUNC_SMBUS_BLOCK_DATA      (I2C_FUNC_SMBUS_READ_BLOCK_DATA | \
-                                        I2C_FUNC_SMBUS_WRITE_BLOCK_DATA)
-#define I2C_FUNC_SMBUS_I2C_BLOCK       (I2C_FUNC_SMBUS_READ_I2C_BLOCK | \
-                                        I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)
+/* end linux/i2c-dev.h */
 
 /*
  * This is needed for ioctl_or_perror_and_die() since it only accepts pointers.
@@ -342,7 +278,7 @@ static int i2c_bus_lookup(const char *bus_str)
        return xstrtou_range(bus_str, 10, 0, 0xfffff);
 }
 
-#if ENABLE_I2CGET || ENABLE_I2CSET || ENABLE_I2CDUMP
+#if ENABLE_I2CGET || ENABLE_I2CSET || ENABLE_I2CDUMP || ENABLE_I2CTRANSFER
 static int i2c_parse_bus_addr(const char *addr_str)
 {
        /* Slave address must be in range 0x03 - 0x77. */
@@ -355,6 +291,13 @@ static void i2c_set_pec(int fd, int pec)
                                itoptr(pec ? 1 : 0),
                                "can't set PEC");
 }
+
+static void i2c_set_slave_addr(int fd, int addr, int force)
+{
+       ioctl_or_perror_and_die(fd, force ? I2C_SLAVE_FORCE : I2C_SLAVE,
+                               itoptr(addr),
+                               "can't set address to 0x%02x", addr);
+}
 #endif /* ENABLE_I2CGET || ENABLE_I2CSET || ENABLE_I2CDUMP */
 
 #if ENABLE_I2CGET || ENABLE_I2CSET
@@ -390,13 +333,6 @@ static int i2c_dev_open(int i2cbus)
        return fd;
 }
 
-static void i2c_set_slave_addr(int fd, int addr, int force)
-{
-       ioctl_or_perror_and_die(fd, force ? I2C_SLAVE_FORCE : I2C_SLAVE,
-                               itoptr(addr),
-                               "can't set address to 0x%02x", addr);
-}
-
 /* Size reducing helpers for xxx_check_funcs(). */
 static void get_funcs_matrix(int fd, unsigned long *funcs)
 {
@@ -408,7 +344,7 @@ static void get_funcs_matrix(int fd, unsigned long *funcs)
 static void check_funcs_test_end(int funcs, int pec, const char *err)
 {
        if (pec && !(funcs & (I2C_FUNC_SMBUS_PEC | I2C_FUNC_I2C)))
-               bb_error_msg("warning: adapter does not support PEC");
+               bb_simple_error_msg("warning: adapter does not support PEC");
 
        if (err)
                bb_error_msg_and_die(
@@ -456,7 +392,7 @@ static void check_read_funcs(int fd, int mode, int data_addr, int pec)
                break;
 #endif /* ENABLE_I2CDUMP */
        default:
-               bb_error_msg_and_die("Programmer goofed!");
+               bb_simple_error_msg_and_die("internal error");
        }
        check_funcs_test_end(funcs, pec, err);
 }
@@ -501,9 +437,8 @@ static void check_write_funcs(int fd, int mode, int pec)
 static void confirm_or_abort(void)
 {
        fprintf(stderr, "Continue? [y/N] ");
-       fflush_all();
-       if (!bb_ask_confirmation())
-               bb_error_msg_and_die("aborting");
+       if (!bb_ask_y_confirmation())
+               bb_simple_error_msg_and_die("aborting");
 }
 
 /*
@@ -514,51 +449,50 @@ static void confirm_or_abort(void)
  */
 static void confirm_action(int bus_addr, int mode, int data_addr, int pec)
 {
-       bb_error_msg("WARNING! This program can confuse your I2C bus");
+       bb_simple_error_msg("WARNING! This program can confuse your I2C bus");
 
        /* Don't let the user break his/her EEPROMs */
        if (bus_addr >= 0x50 && bus_addr <= 0x57 && pec) {
-               bb_error_msg_and_die("this is I2C not smbus - using PEC on I2C "
+               bb_simple_error_msg_and_die("this is I2C not smbus - using PEC on I2C "
                        "devices may result in data loss, aborting");
        }
 
        if (mode == I2C_SMBUS_BYTE && data_addr >= 0 && pec)
-               bb_error_msg("WARNING! May interpret a write byte command "
+               bb_simple_error_msg("WARNING! May interpret a write byte command "
                        "with PEC as a write byte data command");
 
        if (pec)
-               bb_error_msg("PEC checking enabled");
+               bb_simple_error_msg("PEC checking enabled");
 
        confirm_or_abort();
 }
 
 #if ENABLE_I2CGET
 //usage:#define i2cget_trivial_usage
-//usage:       "[-f] [-y] BUS CHIP-ADDRESS [DATA-ADDRESS [MODE]]"
+//usage:       "[-fy] BUS CHIP-ADDRESS [DATA-ADDRESS [MODE]]"
 //usage:#define i2cget_full_usage "\n\n"
-//usage:       "Read from I2C/SMBus chip registers\n"
-//usage:     "\n       I2CBUS  i2c bus number"
-//usage:     "\n       ADDRESS 0x03 - 0x77"
+//usage:       "Read from I2C/SMBus chip registers"
+//usage:     "\n"
+//usage:     "\n       I2CBUS  I2C bus number"
+//usage:     "\n       ADDRESS 0x03-0x77"
 //usage:     "\nMODE is:"
-//usage:     "\n       b       read byte data (default)"
-//usage:     "\n       w       read word data"
-//usage:     "\n       c       write byte/read byte"
+//usage:     "\n       b       Read byte data (default)"
+//usage:     "\n       w       Read word data"
+//usage:     "\n       c       Write byte/read byte"
 //usage:     "\n       Append p for SMBus PEC"
 //usage:     "\n"
-//usage:     "\n       -f      force access"
-//usage:     "\n       -y      disable interactive mode"
+//usage:     "\n       -f      Force access"
+//usage:     "\n       -y      Disable interactive mode"
 int i2cget_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 int i2cget_main(int argc UNUSED_PARAM, char **argv)
 {
        const unsigned opt_f = (1 << 0), opt_y = (1 << 1);
-       const char *const optstr = "fy";
 
        int bus_num, bus_addr, data_addr = -1, status;
        int mode = I2C_SMBUS_BYTE, pec = 0, fd;
        unsigned opts;
 
-        opt_complementary = "-2:?4"; /* from 2 to 4 args */
-       opts = getopt32(argv, optstr);
+       opts = getopt32(argv, "^" "fy" "\0" "-2:?4"/*from 2 to 4 args*/);
        argv += optind;
 
        bus_num = i2c_bus_lookup(argv[0]);
@@ -573,7 +507,7 @@ int i2cget_main(int argc UNUSED_PARAM, char **argv)
                        case 'w':       mode = I2C_SMBUS_WORD_DATA;     break;
                        case 'c':       mode = I2C_SMBUS_BYTE;          break;
                        default:
-                               bb_error_msg("invalid mode");
+                               bb_simple_error_msg("invalid mode");
                                bb_show_usage();
                        }
                        pec = argv[3][1] == 'p';
@@ -595,7 +529,7 @@ int i2cget_main(int argc UNUSED_PARAM, char **argv)
                if (data_addr >= 0) {
                        status = i2c_smbus_write_byte(fd, data_addr);
                        if (status < 0)
-                               bb_error_msg("warning - write failed");
+                               bb_simple_error_msg("warning - write failed");
                }
                status = i2c_smbus_read_byte(fd);
                break;
@@ -608,7 +542,7 @@ int i2cget_main(int argc UNUSED_PARAM, char **argv)
        close(fd);
 
        if (status < 0)
-               bb_perror_msg_and_die("read failed");
+               bb_simple_perror_msg_and_die("read failed");
 
        printf("0x%0*x\n", mode == I2C_SMBUS_WORD_DATA ? 4 : 2, status);
 
@@ -618,40 +552,44 @@ int i2cget_main(int argc UNUSED_PARAM, char **argv)
 
 #if ENABLE_I2CSET
 //usage:#define i2cset_trivial_usage
-//usage:       "[-f] [-y] [-m MASK] BUS CHIP-ADDR DATA-ADDR [VALUE] ... [MODE]"
+//usage:       "[-fy] [-m MASK] BUS CHIP-ADDRESS DATA-ADDRESS [VALUE] ... [MODE]"
 //usage:#define i2cset_full_usage "\n\n"
-//usage:       "Set I2C registers\n"
-//usage:     "\n       I2CBUS  i2c bus number"
-//usage:     "\n       ADDRESS 0x03 - 0x77"
+//usage:       "Set I2C registers"
+//usage:     "\n"
+//usage:     "\n       I2CBUS  I2C bus number"
+//usage:     "\n       ADDRESS 0x03-0x77"
 //usage:     "\nMODE is:"
-//usage:     "\n       c       byte, no value"
-//usage:     "\n       b       byte data (default)"
-//usage:     "\n       w       word data"
+//usage:     "\n       c       Byte, no value"
+//usage:     "\n       b       Byte data (default)"
+//usage:     "\n       w       Word data"
 //usage:     "\n       i       I2C block data"
 //usage:     "\n       s       SMBus block data"
 //usage:     "\n       Append p for SMBus PEC"
 //usage:     "\n"
-//usage:     "\n       -f      force access"
-//usage:     "\n       -y      disable interactive mode"
-//usage:     "\n       -r      read back and compare the result"
-//usage:     "\n       -m MASK mask specifying which bits to write"
+//usage:     "\n       -f      Force access"
+//usage:     "\n       -y      Disable interactive mode"
+//usage:     "\n       -r      Read back and compare the result"
+//usage:     "\n       -m MASK Mask specifying which bits to write"
 int i2cset_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 int i2cset_main(int argc, char **argv)
 {
        const unsigned opt_f = (1 << 0), opt_y = (1 << 1),
                              opt_m = (1 << 2), opt_r = (1 << 3);
-       const char *const optstr = "fym:r";
 
        int bus_num, bus_addr, data_addr, mode = I2C_SMBUS_BYTE, pec = 0;
-       int val, blen = 0, mask = 0, fd, status;
+       int val, blen, mask, fd, status;
        unsigned char block[I2C_SMBUS_BLOCK_MAX];
        char *opt_m_arg = NULL;
        unsigned opts;
 
-        opt_complementary = "-3"; /* from 3 to ? args */
-       opts = getopt32(argv, optstr, &opt_m_arg);
+       opts = getopt32(argv, "^"
+               "fym:r"
+               "\0" "-3", /* minimum 3 args */
+               &opt_m_arg
+       );
        argv += optind;
        argc -= optind;
+       argc--; /* now argv[argc] is last arg */
 
        bus_num = i2c_bus_lookup(argv[0]);
        bus_addr = i2c_parse_bus_addr(argv[1]);
@@ -661,26 +599,32 @@ int i2cset_main(int argc, char **argv)
                if (!argv[4] && argv[3][0] != 'c') {
                        mode = I2C_SMBUS_BYTE_DATA; /* Implicit b */
                } else {
-                       switch (argv[argc-1][0]) {
-                       case 'c': /* Already set */                     break;
-                       case 'b': mode = I2C_SMBUS_BYTE_DATA;           break;
-                       case 'w': mode = I2C_SMBUS_WORD_DATA;           break;
-                       case 's': mode = I2C_SMBUS_BLOCK_DATA;          break;
-                       case 'i': mode = I2C_SMBUS_I2C_BLOCK_DATA;      break;
+                       switch (argv[argc][0]) {
+                       case 'c': /* Already set */
+                               break;
+                       case 'b': mode = I2C_SMBUS_BYTE_DATA;
+                               break;
+                       case 'w': mode = I2C_SMBUS_WORD_DATA;
+                               break;
+                       case 's': mode = I2C_SMBUS_BLOCK_DATA;
+                               break;
+                       case 'i': mode = I2C_SMBUS_I2C_BLOCK_DATA;
+                               break;
                        default:
-                               bb_error_msg("invalid mode");
+                               bb_simple_error_msg("invalid mode");
                                bb_show_usage();
                        }
 
-                       pec = argv[argc-1][1] == 'p';
-                       if (mode == I2C_SMBUS_BLOCK_DATA ||
-                                       mode == I2C_SMBUS_I2C_BLOCK_DATA) {
+                       pec = (argv[argc][1] == 'p');
+                       if (mode == I2C_SMBUS_BLOCK_DATA
+                        || mode == I2C_SMBUS_I2C_BLOCK_DATA
+                       ) {
                                if (pec && mode == I2C_SMBUS_I2C_BLOCK_DATA)
-                                       bb_error_msg_and_die(
+                                       bb_simple_error_msg_and_die(
                                                "PEC not supported for I2C "
                                                "block writes");
                                if (opts & opt_m)
-                                       bb_error_msg_and_die(
+                                       bb_simple_error_msg_and_die(
                                                "mask not supported for block "
                                                "writes");
                        }
@@ -688,6 +632,8 @@ int i2cset_main(int argc, char **argv)
        }
 
        /* Prepare the value(s) to be written according to current mode. */
+       mask = 0;
+       blen = 0;
        switch (mode) {
        case I2C_SMBUS_BYTE_DATA:
                val = xstrtou_range(argv[3], 0, 0, 0xff);
@@ -697,8 +643,9 @@ int i2cset_main(int argc, char **argv)
                break;
        case I2C_SMBUS_BLOCK_DATA:
        case I2C_SMBUS_I2C_BLOCK_DATA:
-               for (blen = 3; blen < (argc - 1); blen++)
-                       block[blen] = xstrtou_range(argv[blen], 0, 0, 0xff);
+               for (blen = 3; blen < argc; blen++)
+                       block[blen - 3] = xstrtou_range(argv[blen], 0, 0, 0xff);
+               blen -= 3;
                val = -1;
                break;
        default:
@@ -738,7 +685,7 @@ int i2cset_main(int argc, char **argv)
                }
 
                if (tmpval < 0)
-                       bb_perror_msg_and_die("can't read old value");
+                       bb_simple_perror_msg_and_die("can't read old value");
 
                val = (val & mask) | (tmpval & ~mask);
 
@@ -777,7 +724,7 @@ int i2cset_main(int argc, char **argv)
                break;
        }
        if (status < 0)
-               bb_perror_msg_and_die("write failed");
+               bb_simple_perror_msg_and_die("write failed");
 
        if (pec)
                i2c_set_pec(fd, 0); /* Clear PEC. */
@@ -799,7 +746,7 @@ int i2cset_main(int argc, char **argv)
        }
 
        if (status < 0) {
-               printf("Warning - readback failed\n");
+               puts("Warning - readback failed");
        } else
        if (status != val) {
                printf("Warning - data mismatch - wrote "
@@ -816,41 +763,204 @@ int i2cset_main(int argc, char **argv)
 #endif /* ENABLE_I2CSET */
 
 #if ENABLE_I2CDUMP
+static int read_block_data(int buf_fd, int mode, int *block)
+{
+       uint8_t cblock[I2C_SMBUS_BLOCK_MAX + I2CDUMP_NUM_REGS];
+       int res, blen = 0, tmp, i;
+
+       if (mode == I2C_SMBUS_BLOCK_DATA) {
+               blen = i2c_smbus_read_block_data(buf_fd, 0, cblock);
+               if (blen <= 0)
+                       goto fail;
+       } else {
+               for (res = 0; res < I2CDUMP_NUM_REGS; res += tmp) {
+                       tmp = i2c_smbus_read_i2c_block_data(
+                                       buf_fd, res, I2C_SMBUS_BLOCK_MAX,
+                                       cblock + res);
+                       if (tmp <= 0) {
+                               blen = tmp;
+                               goto fail;
+                       }
+               }
+
+               if (res >= I2CDUMP_NUM_REGS)
+                       res = I2CDUMP_NUM_REGS;
+
+               for (i = 0; i < res; i++)
+                       block[i] = cblock[i];
+
+               if (mode != I2C_SMBUS_BLOCK_DATA)
+                       for (i = res; i < I2CDUMP_NUM_REGS; i++)
+                               block[i] = -1;
+       }
+
+       return blen;
+
+ fail:
+       bb_error_msg_and_die("block read failed: %d", blen);
+}
+
+/* Dump all but word data. */
+static void dump_data(int bus_fd, int mode, unsigned first,
+                     unsigned last, int *block, int blen)
+{
+       int i, j, res;
+
+       puts("     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f"
+            "    0123456789abcdef");
+
+       for (i = 0; i < I2CDUMP_NUM_REGS; i += 0x10) {
+               if (mode == I2C_SMBUS_BLOCK_DATA && i >= blen)
+                       break;
+               if (i/16 < first/16)
+                       continue;
+               if (i/16 > last/16)
+                       break;
+
+               printf("%02x: ", i);
+               for (j = 0; j < 16; j++) {
+                       fflush_all();
+                       /* Skip unwanted registers */
+                       if (i+j < first || i+j > last) {
+                               printf("   ");
+                               if (mode == I2C_SMBUS_WORD_DATA) {
+                                       printf("   ");
+                                       j++;
+                               }
+                               continue;
+                       }
+
+                       switch (mode) {
+                       case I2C_SMBUS_BYTE_DATA:
+                               res = i2c_smbus_read_byte_data(bus_fd, i+j);
+                               block[i+j] = res;
+                               break;
+                       case I2C_SMBUS_WORD_DATA:
+                               res = i2c_smbus_read_word_data(bus_fd, i+j);
+                               if (res < 0) {
+                                       block[i+j] = res;
+                                       block[i+j+1] = res;
+                               } else {
+                                       block[i+j] = res & 0xff;
+                                       block[i+j+1] = res >> 8;
+                               }
+                               break;
+                       case I2C_SMBUS_BYTE:
+                               res = i2c_smbus_read_byte(bus_fd);
+                               block[i+j] = res;
+                               break;
+                       default:
+                               res = block[i+j];
+                       }
+
+                       if (mode == I2C_SMBUS_BLOCK_DATA &&
+                           i+j >= blen) {
+                               printf("   ");
+                       } else if (res < 0) {
+                               printf("XX ");
+                               if (mode == I2C_SMBUS_WORD_DATA)
+                                       printf("XX ");
+                       } else {
+                               printf("%02x ", block[i+j]);
+                               if (mode == I2C_SMBUS_WORD_DATA)
+                                       printf("%02x ", block[i+j+1]);
+                       }
+
+                       if (mode == I2C_SMBUS_WORD_DATA)
+                               j++;
+               }
+               printf("   ");
+
+               for (j = 0; j < 16; j++) {
+                       if (mode == I2C_SMBUS_BLOCK_DATA && i+j >= blen)
+                               break;
+                       /* Skip unwanted registers */
+                       if (i+j < first || i+j > last) {
+                               bb_putchar(' ');
+                               continue;
+                       }
+
+                       res = block[i+j];
+                       if (res < 0) {
+                               bb_putchar('X');
+                       } else if (res == 0x00 || res == 0xff) {
+                               bb_putchar('.');
+                       } else if (res < 32 || res >= 127) {
+                               bb_putchar('?');
+                       } else {
+                               bb_putchar(res);
+                       }
+               }
+               bb_putchar('\n');
+       }
+}
+
+static void dump_word_data(int bus_fd, unsigned first, unsigned last)
+{
+       int i, j, rv;
+
+       /* Word data. */
+       puts("     0,8  1,9  2,a  3,b  4,c  5,d  6,e  7,f");
+       for (i = 0; i < 256; i += 8) {
+               if (i/8 < first/8)
+                       continue;
+               if (i/8 > last/8)
+                       break;
+
+               printf("%02x: ", i);
+               for (j = 0; j < 8; j++) {
+                       /* Skip unwanted registers. */
+                       if (i+j < first || i+j > last) {
+                               printf("     ");
+                               continue;
+                       }
+
+                       rv = i2c_smbus_read_word_data(bus_fd, i+j);
+                       if (rv < 0)
+                               printf("XXXX ");
+                       else
+                               printf("%04x ", rv & 0xffff);
+               }
+               bb_putchar('\n');
+       }
+}
+
 //usage:#define i2cdump_trivial_usage
-//usage:       "[-f] [-r FIRST-LAST] [-y] BUS ADDR [MODE]"
+//usage:       "[-fy] [-r FIRST-LAST] BUS ADDR [MODE]"
 //usage:#define i2cdump_full_usage "\n\n"
-//usage:       "Examine I2C registers\n"
-//usage:     "\n       I2CBUS  i2c bus number"
-//usage:     "\n       ADDRESS 0x03 - 0x77"
+//usage:       "Examine I2C registers"
+//usage:     "\n"
+//usage:     "\n       I2CBUS  I2C bus number"
+//usage:     "\n       ADDRESS 0x03-0x77"
 //usage:     "\nMODE is:"
-//usage:     "\n       b       byte (default)"
-//usage:     "\n       w       word"
-//usage:     "\n       W       word on even register addresses"
+//usage:     "\n       b       Byte (default)"
+//usage:     "\n       w       Word"
+//usage:     "\n       W       Word on even register addresses"
 //usage:     "\n       i       I2C block"
 //usage:     "\n       s       SMBus block"
-//usage:     "\n       c       consecutive byte"
+//usage:     "\n       c       Consecutive byte"
 //usage:     "\n       Append p for SMBus PEC"
 //usage:     "\n"
-//usage:     "\n       -f      force access"
-//usage:     "\n       -y      disable interactive mode"
-//usage:     "\n       -r      limit the number of registers being accessed"
+//usage:     "\n       -f      Force access"
+//usage:     "\n       -y      Disable interactive mode"
+//usage:     "\n       -r      Limit the number of registers being accessed"
 int i2cdump_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 int i2cdump_main(int argc UNUSED_PARAM, char **argv)
 {
        const unsigned opt_f = (1 << 0), opt_y = (1 << 1),
                              opt_r = (1 << 2);
-       const char *const optstr = "fyr:";
 
        int bus_num, bus_addr, mode = I2C_SMBUS_BYTE_DATA, even = 0, pec = 0;
-       unsigned first = 0x00, last = 0xff;
-       int fd, i, j, res, blen = 0, tmp;
-       unsigned char cblock[I2C_SMBUS_BLOCK_MAX + I2C_MAX_REGS];
-       unsigned char block[I2C_SMBUS_BLOCK_MAX];
+       unsigned first = 0x00, last = 0xff, opts;
+       int block[I2CDUMP_NUM_REGS];
        char *opt_r_str, *dash;
-       unsigned opts;
+       int fd, res;
 
-        opt_complementary = "-2:?3"; /* from 2 to 3 args */
-       opts = getopt32(argv, optstr, &opt_r_str);
+       opts = getopt32(argv, "^"
+               "fyr:"
+               "\0" "-2:?3" /* from 2 to 3 args */,
+               &opt_r_str
+       );
        argv += optind;
 
        bus_num = i2c_bus_lookup(argv[0]);
@@ -858,7 +968,7 @@ int i2cdump_main(int argc UNUSED_PARAM, char **argv)
 
        if (argv[2]) {
                switch (argv[2][0]) {
-               case 'b': /* Already set */                     break;
+               case 'b': /* Already set. */                    break;
                case 'c': mode = I2C_SMBUS_BYTE;                break;
                case 'w': mode = I2C_SMBUS_WORD_DATA;           break;
                case 'W':
@@ -868,12 +978,12 @@ int i2cdump_main(int argc UNUSED_PARAM, char **argv)
                case 's': mode = I2C_SMBUS_BLOCK_DATA;          break;
                case 'i': mode = I2C_SMBUS_I2C_BLOCK_DATA;      break;
                default:
-                       bb_error_msg_and_die("invalid mode");
+                       bb_simple_error_msg_and_die("invalid mode");
                }
 
                if (argv[2][1] == 'p') {
                        if (argv[2][0] == 'W' || argv[2][0] == 'i') {
-                               bb_error_msg_and_die(
+                               bb_simple_error_msg_and_die(
                                        "pec not supported for -W and -i");
                        } else {
                                pec = 1;
@@ -884,10 +994,10 @@ int i2cdump_main(int argc UNUSED_PARAM, char **argv)
        if (opts & opt_r) {
                first = strtol(opt_r_str, &dash, 0);
                if (dash == opt_r_str || *dash != '-' || first > 0xff)
-                       bb_error_msg_and_die("invalid range");
+                       bb_simple_error_msg_and_die("invalid range");
                last = xstrtou_range(++dash, 0, first, 0xff);
 
-               /* Range is not available for every mode */
+               /* Range is not available for every mode. */
                switch (mode) {
                case I2C_SMBUS_BYTE:
                case I2C_SMBUS_BYTE_DATA:
@@ -897,7 +1007,7 @@ int i2cdump_main(int argc UNUSED_PARAM, char **argv)
                                break;
                        /* Fall through */
                default:
-                       bb_error_msg_and_die(
+                       bb_simple_error_msg_and_die(
                                "range not compatible with selected mode");
                }
        }
@@ -912,156 +1022,22 @@ int i2cdump_main(int argc UNUSED_PARAM, char **argv)
        if (!(opts & opt_y))
                confirm_action(bus_addr, mode, -1 /* data_addr */, pec);
 
-       /* All but word data */
+       /* All but word data. */
        if (mode != I2C_SMBUS_WORD_DATA || even) {
-               /*
-                * FIXME This section has been ported from upstream i2cdump.
-                * It has been reworked a bit but is still pretty spaghetti
-                * and needs splitting into several functions.
-                */
-               if (mode == I2C_SMBUS_BLOCK_DATA ||
-                   mode == I2C_SMBUS_I2C_BLOCK_DATA) {
-                       res = i2c_smbus_read_block_data(fd, 0, cblock);
-                       blen = res;
-               } else {
-                       for (res = 0; res < I2C_MAX_REGS; res += tmp) {
-                               tmp = i2c_smbus_read_i2c_block_data(
-                                       fd, res, I2C_SMBUS_BLOCK_MAX,
-                                       cblock + res);
-                               if (tmp < 0) {
-                                       bb_error_msg_and_die(
-                                               "block read failed");
-                               }
-                       }
-                       if (res >= I2C_MAX_REGS)
-                               res = I2C_MAX_REGS;
-                       for (i = 0; i < res; i++)
-                               block[i] = cblock[i];
-                       if (mode != I2C_SMBUS_BLOCK_DATA)
-                               for (i = res; i < I2C_MAX_REGS; i++)
-                                       cblock[i] = -1;
-               }
+               int blen = 0;
+
+               if (mode == I2C_SMBUS_BLOCK_DATA || mode == I2C_SMBUS_I2C_BLOCK_DATA)
+                       blen = read_block_data(fd, mode, block);
 
                if (mode == I2C_SMBUS_BYTE) {
                        res = i2c_smbus_write_byte(fd, first);
                        if (res < 0)
-                               bb_perror_msg_and_die(
-                                       "write start address failed");
+                               bb_simple_perror_msg_and_die("write start address");
                }
 
-               printf("     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f"
-                      "    0123456789abcdef\n");
-
-               for (i = 0; i < I2C_MAX_REGS; i += 0x10) {
-                       if (mode == I2C_SMBUS_BLOCK_DATA && i >= blen)
-                               break;
-                       if (i/16 < first/16)
-                               continue;
-                       if (i/16 > last/16)
-                               break;
-
-                       printf("%02x: ", i);
-                       for (j = 0; j < 16; j++) {
-                               fflush_all();
-                               /* Skip unwanted registers */
-                               if (i+j < first || i+j > last) {
-                                       printf("   ");
-                                       if (mode == I2C_SMBUS_WORD_DATA) {
-                                               printf("   ");
-                                               j++;
-                                       }
-                                       continue;
-                               }
-
-                               switch (mode) {
-                               case I2C_SMBUS_BYTE_DATA:
-                                       res = i2c_smbus_read_byte_data(fd, i+j);
-                                       block[i+j] = res;
-                                       break;
-                               case I2C_SMBUS_WORD_DATA:
-                                       res = i2c_smbus_read_word_data(fd, i+j);
-                                       if (res < 0) {
-                                               block[i+j] = res;
-                                               block[i+j+1] = res;
-                                       } else {
-                                               block[i+j] = res & 0xff;
-                                               block[i+j+1] = res >> 8;
-                                       }
-                                       break;
-                               case I2C_SMBUS_BYTE:
-                                       res = i2c_smbus_read_byte(fd);
-                                       block[i+j] = res;
-                                       break;
-                               default:
-                                       res = block[i+j];
-                               }
-
-                               if (mode == I2C_SMBUS_BLOCK_DATA &&
-                                   i+j >= blen) {
-                                       printf("   ");
-                               } else if (res < 0) {
-                                       printf("XX ");
-                                       if (mode == I2C_SMBUS_WORD_DATA)
-                                               printf("XX ");
-                               } else {
-                                       printf("%02x ", block[i+j]);
-                                       if (mode == I2C_SMBUS_WORD_DATA)
-                                               printf("%02x ", block[i+j+1]);
-                               }
-
-                               if (mode == I2C_SMBUS_WORD_DATA)
-                                       j++;
-                       }
-                       printf("   ");
-
-                       for (j = 0; j < 16; j++) {
-                               if (mode == I2C_SMBUS_BLOCK_DATA && i+j >= blen)
-                                       break;
-                               /* Skip unwanted registers */
-                               if (i+j < first || i+j > last) {
-                                       printf(" ");
-                                       continue;
-                               }
-
-                               res = block[i+j];
-                               if (res < 0) {
-//FIXME: impossible, block[] is uchar[]
-                                       printf("X");
-                               } else if (res == 0x00 || res == 0xff) {
-                                       printf(".");
-                               } else if (res < 32 || res >= 127) {
-                                       printf("?");
-                               } else {
-                                       printf("%c", res);
-                               }
-                       }
-                       printf("\n");
-               }
+               dump_data(fd, mode, first, last, block, blen);
        } else {
-               /* Word data. */
-               printf("     0,8  1,9  2,a  3,b  4,c  5,d  6,e  7,f\n");
-               for (i = 0; i < 256; i += 8) {
-                       if (i/8 < first/8)
-                               continue;
-                       if (i/8 > last/8)
-                               break;
-
-                       printf("%02x: ", i);
-                       for (j = 0; j < 8; j++) {
-                               /* Skip unwanted registers. */
-                               if (i+j < first || i+j > last) {
-                                       printf("     ");
-                                       continue;
-                               }
-
-                               res = i2c_smbus_read_word_data(fd, i+j);
-                               if (res < 0)
-                                       printf("XXXX ");
-                               else
-                                       printf("%04x ", res & 0xffff);
-                       }
-                       printf("\n");
-               }
+               dump_word_data(fd, first, last);
        }
 
        return 0;
@@ -1102,33 +1078,33 @@ static const struct i2c_func i2c_funcs_tab[] = {
        { .value = I2C_FUNC_I2C,
          .name = "I2C" },
        { .value = I2C_FUNC_SMBUS_QUICK,
-         .name = "SMBus Quick Command" },
+         .name = "SMBus quick command" },
        { .value = I2C_FUNC_SMBUS_WRITE_BYTE,
-         .name = "SMBus Send Byte" },
+         .name = "SMBus send byte" },
        { .value = I2C_FUNC_SMBUS_READ_BYTE,
-         .name = "SMBus Receive Byte" },
+         .name = "SMBus receive byte" },
        { .value = I2C_FUNC_SMBUS_WRITE_BYTE_DATA,
-         .name = "SMBus Write Byte" },
+         .name = "SMBus write byte" },
        { .value = I2C_FUNC_SMBUS_READ_BYTE_DATA,
-         .name = "SMBus Read Byte" },
+         .name = "SMBus read byte" },
        { .value = I2C_FUNC_SMBUS_WRITE_WORD_DATA,
-         .name = "SMBus Write Word" },
+         .name = "SMBus write word" },
        { .value = I2C_FUNC_SMBUS_READ_WORD_DATA,
-         .name = "SMBus Read Word" },
+         .name = "SMBus read word" },
        { .value = I2C_FUNC_SMBUS_PROC_CALL,
-         .name = "SMBus Process Call" },
+         .name = "SMBus process call" },
        { .value = I2C_FUNC_SMBUS_WRITE_BLOCK_DATA,
-         .name = "SMBus Block Write" },
+         .name = "SMBus block write" },
        { .value = I2C_FUNC_SMBUS_READ_BLOCK_DATA,
-         .name = "SMBus Block Read" },
+         .name = "SMBus block read" },
        { .value = I2C_FUNC_SMBUS_BLOCK_PROC_CALL,
-         .name = "SMBus Block Process Call" },
+         .name = "SMBus block process call" },
        { .value = I2C_FUNC_SMBUS_PEC,
          .name = "SMBus PEC" },
        { .value = I2C_FUNC_SMBUS_WRITE_I2C_BLOCK,
-         .name = "I2C Block Write" },
+         .name = "I2C block write" },
        { .value = I2C_FUNC_SMBUS_READ_I2C_BLOCK,
-         .name = "I2C Block Read" },
+         .name = "I2C block read" },
        { .value = 0, .name = NULL }
 };
 
@@ -1208,12 +1184,11 @@ static void NORETURN list_i2c_busses_and_exit(void)
                                                 i2cdev_path, de->d_name,
                                                 subde->d_name);
                                        fp = fopen(path, "r");
-                                       goto found;
+                                       break;
                                }
                        }
                }
 
-found:
                if (fp != NULL) {
                        /*
                         * Get the rest of the info and display a line
@@ -1261,35 +1236,34 @@ static void will_skip(const char *cmd)
 }
 
 //usage:#define i2cdetect_trivial_usage
-//usage:       "[-F I2CBUS] [-l] [-y] [-a] [-q|-r] I2CBUS [FIRST LAST]"
+//usage:       "-l | -F I2CBUS | [-ya] [-q|-r] I2CBUS [FIRST LAST]"
 //usage:#define i2cdetect_full_usage "\n\n"
-//usage:       "Detect I2C chips.\n"
-//usage:     "\n       I2CBUS  i2c bus number"
-//usage:     "\n       FIRST and LAST limit the probing range"
+//usage:       "Detect I2C chips"
 //usage:     "\n"
-//usage:     "\n       -l      output list of installed busses"
-//usage:     "\n       -y      disable interactive mode"
-//usage:     "\n       -a      force scanning of non-regular addresses"
-//usage:     "\n       -q      use smbus quick write commands for probing (default)"
-//usage:     "\n       -r      use smbus read byte commands for probing"
-//usage:     "\n       -F      display list of functionalities"
+//usage:     "\n       -l      List installed buses"
+//usage:     "\n       -F BUS# List functionalities on this bus"
+//usage:     "\n       -y      Disable interactive mode"
+//usage:     "\n       -a      Force scanning of non-regular addresses"
+//usage:     "\n       -q      Use smbus quick write commands for probing (default)"
+//usage:     "\n       -r      Use smbus read byte commands for probing"
+//usage:     "\n       FIRST and LAST limit probing range"
 int i2cdetect_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 int i2cdetect_main(int argc UNUSED_PARAM, char **argv)
 {
        const unsigned opt_y = (1 << 0), opt_a = (1 << 1),
                              opt_q = (1 << 2), opt_r = (1 << 3),
                              opt_F = (1 << 4), opt_l = (1 << 5);
-       const char *const optstr = "yaqrFl";
 
-       int fd, bus_num, i, j, mode = DETECT_MODE_AUTO;
-       int status;
-       unsigned first = 0x00, last = 0x77;
+       int fd, bus_num, i, j, mode = I2CDETECT_MODE_AUTO, status, cmd;
+       unsigned first = 0x03, last = 0x77, opts;
        unsigned long funcs;
-       unsigned opts;
 
-       opt_complementary = "q--r:r--q:" /* mutually exclusive */
-                       "?3"; /* up to 3 args */
-       opts = getopt32(argv, optstr);
+       opts = getopt32(argv, "^"
+                       "yaqrFl"
+                       "\0"
+                       "q--r:r--q:"/*mutually exclusive*/
+                       "?3"/*up to 3 args*/
+       );
        argv += optind;
 
        if (opts & opt_l)
@@ -1314,12 +1288,14 @@ int i2cdetect_main(int argc UNUSED_PARAM, char **argv)
        }
 
        if (opts & opt_r)
-               mode = DETECT_MODE_READ;
+               mode = I2CDETECT_MODE_READ;
        else if (opts & opt_q)
-               mode = DETECT_MODE_QUICK;
+               mode = I2CDETECT_MODE_QUICK;
 
-       if (opts & opt_a)
+       if (opts & opt_a) {
+               first = 0x00;
                last = 0x7f;
+       }
 
        /* Read address range. */
        if (argv[1]) {
@@ -1331,56 +1307,68 @@ int i2cdetect_main(int argc UNUSED_PARAM, char **argv)
        if (!(funcs & (I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_READ_BYTE))) {
                no_support("detection commands");
        } else
-       if (mode == DETECT_MODE_QUICK && !(funcs & I2C_FUNC_SMBUS_QUICK)) {
-               no_support("SMBus Quick Write command");
+       if (mode == I2CDETECT_MODE_QUICK && !(funcs & I2C_FUNC_SMBUS_QUICK)) {
+               no_support("SMBus quick write");
        } else
-       if (mode == DETECT_MODE_READ && !(funcs & I2C_FUNC_SMBUS_READ_BYTE)) {
-               no_support("SMBus Receive Byte command");
-       } else {
+       if (mode == I2CDETECT_MODE_READ && !(funcs & I2C_FUNC_SMBUS_READ_BYTE)) {
+               no_support("SMBus receive byte");
+       }
+
+       if (mode == I2CDETECT_MODE_AUTO) {
                if (!(funcs & I2C_FUNC_SMBUS_QUICK))
-                       will_skip("SMBus Quick Write");
+                       will_skip("SMBus quick write");
                if (!(funcs & I2C_FUNC_SMBUS_READ_BYTE))
-                       will_skip("SMBus Receive Byte");
+                       will_skip("SMBus receive byte");
        }
 
        if (!(opts & opt_y))
                confirm_action(-1, -1, -1, 0);
 
-       printf("     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f\n");
+       puts("     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f");
        for (i = 0; i < 128; i += 16) {
                printf("%02x: ", i);
-               for(j = 0; j < 16; j++) {
+               for (j = 0; j < 16; j++) {
                        fflush_all();
 
-                       if (mode == DETECT_MODE_AUTO) {
+                       cmd = mode;
+                       if (mode == I2CDETECT_MODE_AUTO) {
                                if ((i+j >= 0x30 && i+j <= 0x37) ||
                                    (i+j >= 0x50 && i+j <= 0x5F))
-                                       mode = DETECT_MODE_READ;
+                                       cmd = I2CDETECT_MODE_READ;
                                else
-                                       mode = DETECT_MODE_QUICK;
+                                       cmd = I2CDETECT_MODE_QUICK;
                        }
 
                        /* Skip unwanted addresses. */
                        if (i+j < first
                         || i+j > last
-                        || (mode == DETECT_MODE_READ && !(funcs & I2C_FUNC_SMBUS_READ_BYTE))
-                        || (mode == DETECT_MODE_QUICK && !(funcs & I2C_FUNC_SMBUS_QUICK)))
+                        || (cmd == I2CDETECT_MODE_READ && !(funcs & I2C_FUNC_SMBUS_READ_BYTE))
+                        || (cmd == I2CDETECT_MODE_QUICK && !(funcs & I2C_FUNC_SMBUS_QUICK)))
                        {
                                printf("   ");
                                continue;
                        }
 
-                       i2c_set_slave_addr(fd, i + j, 0);
+                       status = ioctl(fd, I2C_SLAVE, itoptr(i + j));
+                       if (status < 0) {
+                               if (errno == EBUSY) {
+                                       printf("UU ");
+                                       continue;
+                               }
 
-                       switch (mode) {
-                       case DETECT_MODE_READ:
+                               bb_perror_msg_and_die(
+                                       "can't set address to 0x%02x", i + j);
+                       }
+
+                       switch (cmd) {
+                       case I2CDETECT_MODE_READ:
                                /*
                                 * This is known to lock SMBus on various
                                 * write-only chips (mainly clock chips).
                                 */
                                status = i2c_smbus_read_byte(fd);
                                break;
-                       default: /* DETECT_MODE_QUICK: */
+                       default: /* I2CDETECT_MODE_QUICK: */
                                /*
                                 * This is known to corrupt the Atmel
                                 * AT24RF08 EEPROM.
@@ -1395,9 +1383,167 @@ int i2cdetect_main(int argc UNUSED_PARAM, char **argv)
                        else
                                printf("%02x ", i+j);
                }
-               printf("\n");
+               bb_putchar('\n');
        }
 
        return 0;
 }
 #endif /* ENABLE_I2CDETECT */
+
+#if ENABLE_I2CTRANSFER
+static void check_i2c_func(int fd)
+{
+       unsigned long funcs;
+
+       get_funcs_matrix(fd, &funcs);
+
+       if (!(funcs & I2C_FUNC_I2C))
+               bb_simple_error_msg_and_die("adapter does not support I2C transfers");
+}
+
+//usage:#define i2ctransfer_trivial_usage
+//usage:       "[-fay] I2CBUS {rLENGTH[@ADDR] | wLENGTH[@ADDR] DATA...}..."
+//usage:#define i2ctransfer_full_usage "\n\n"
+//usage:       "Read/write I2C data in one transfer"
+//usage:     "\n"
+//usage:     "\n       -f      Force access to busy addresses"
+//usage:     "\n       -a      Force access to non-regular addresses"
+//usage:     "\n       -y      Disable interactive mode"
+int i2ctransfer_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int i2ctransfer_main(int argc UNUSED_PARAM, char **argv)
+{
+       enum {
+               opt_f = (1 << 0),
+               opt_y = (1 << 1),
+               opt_a = (1 << 2),
+       };
+       int bus_num, bus_addr;
+       int fd;
+       unsigned opts, first, last;
+       int nmsgs, nmsgs_sent, i;
+       struct i2c_msg msgs[I2C_RDWR_IOCTL_MAX_MSGS];
+       struct i2c_rdwr_ioctl_data rdwr;
+
+       memset(msgs, 0, sizeof(msgs));
+
+       opts = getopt32(argv, "^"
+               "fya"
+               "\0" "-2" /* minimum 2 args */
+       );
+       first = 0x03;
+       last = 0x77;
+       if (opts & opt_a) {
+               first = 0x00;
+               last = 0x7f;
+       }
+
+       argv += optind;
+       bus_num = i2c_bus_lookup(argv[0]);
+       fd = i2c_dev_open(bus_num);
+       check_i2c_func(fd);
+
+       bus_addr = -1;
+       nmsgs = 0;
+       while (*++argv) {
+               char *arg_ptr;
+               unsigned len;
+               uint16_t flags;
+               char *end;
+
+               if (nmsgs >= I2C_RDWR_IOCTL_MAX_MSGS)
+                       bb_simple_error_msg_and_die("too many messages, max: "I2C_RDWR_IOCTL_MAX_MSGS_STR);
+
+               flags = 0;
+               arg_ptr = *argv;
+               switch (*arg_ptr++) {
+               case 'r': flags |= I2C_M_RD; break;
+               case 'w': break;
+               default:
+                       bb_show_usage();
+               }
+
+               end = strchr(arg_ptr, '@');
+               if (end) *end = '\0';
+               len = xstrtou_range(arg_ptr, 0, 0, 0xffff);
+               if (end) {
+                       bus_addr = xstrtou_range(end + 1, 0, first, last);
+                       i2c_set_slave_addr(fd, bus_addr, (opts & opt_f));
+               } else {
+                       /* Reuse last address if possible */
+                       if (bus_addr < 0)
+                               bb_error_msg_and_die("no address given in '%s'", *argv);
+               }
+
+               msgs[nmsgs].addr = bus_addr;
+               msgs[nmsgs].flags = flags;
+               msgs[nmsgs].len = len;
+               if (len)
+                       msgs[nmsgs].buf = xzalloc(len);
+
+               if (!(flags & I2C_M_RD)) {
+                       /* Consume DATA arg(s) */
+                       unsigned buf_idx = 0;
+
+                       while (buf_idx < len) {
+                               uint8_t data8;
+                               unsigned long data;
+
+                               arg_ptr = *++argv;
+                               if (!arg_ptr)
+                                       bb_show_usage();
+                               data = strtoul(arg_ptr, &end, 0);
+                               if (data > 0xff || arg_ptr == end)
+                                       bb_error_msg_and_die("invalid data byte '%s'", *argv);
+
+                               data8 = data;
+                               while (buf_idx < len) {
+                                       msgs[nmsgs].buf[buf_idx++] = data8;
+                                       if (!*end)
+                                               break;
+                                       switch (*end) {
+                                       /* Pseudo randomness (8 bit AXR with a=13 and b=27) */
+                                       case 'p':
+                                               data8 = (data8 ^ 27) + 13;
+                                               data8 = (data8 << 1) | (data8 >> 7);
+                                               break;
+                                       case '+': data8++; break;
+                                       case '-': data8--; break;
+                                       case '=': break;
+                                       default:
+                                               bb_error_msg_and_die("invalid data byte suffix: '%s'",
+                                                                    *argv);
+                                       }
+                               }
+                       }
+               }
+               nmsgs++;
+       }
+
+       if (!(opts & opt_y))
+               confirm_action(bus_addr, 0, 0, 0);
+
+       rdwr.msgs = msgs;
+       rdwr.nmsgs = nmsgs;
+       nmsgs_sent = ioctl_or_perror_and_die(fd, I2C_RDWR, &rdwr, "I2C_RDWR");
+       if (nmsgs_sent < nmsgs)
+               bb_error_msg("warning: only %u/%u messages sent", nmsgs_sent, nmsgs);
+
+       for (i = 0; i < nmsgs_sent; i++) {
+               if (msgs[i].len != 0 && (msgs[i].flags & I2C_M_RD)) {
+                       int j;
+                       for (j = 0; j < msgs[i].len - 1; j++)
+                               printf("0x%02x ", msgs[i].buf[j]);
+                       /* Print final byte with newline */
+                       printf("0x%02x\n", msgs[i].buf[j]);
+               }
+       }
+
+# if ENABLE_FEATURE_CLEAN_UP
+       close(fd);
+       for (i = 0; i < nmsgs; i++)
+               free(msgs[i].buf);
+# endif
+
+       return 0;
+}
+#endif /* ENABLE_I2CTRANSFER */