diff options
Diffstat (limited to 'tools')
-rw-r--r-- | tools/Module.mk | 84 | ||||
-rw-r--r-- | tools/i2cbusses.c | 414 | ||||
-rw-r--r-- | tools/i2cbusses.h | 42 | ||||
-rw-r--r-- | tools/i2cdetect.8 | 87 | ||||
-rw-r--r-- | tools/i2cdetect.c | 357 | ||||
-rw-r--r-- | tools/i2cdump.8 | 84 | ||||
-rw-r--r-- | tools/i2cdump.c | 487 | ||||
-rw-r--r-- | tools/i2cget.8 | 68 | ||||
-rw-r--r-- | tools/i2cget.c | 256 | ||||
-rw-r--r-- | tools/i2cset.8 | 91 | ||||
-rw-r--r-- | tools/i2cset.c | 344 | ||||
-rw-r--r-- | tools/util.c | 48 | ||||
-rw-r--r-- | tools/util.h | 16 |
13 files changed, 2378 insertions, 0 deletions
diff --git a/tools/Module.mk b/tools/Module.mk new file mode 100644 index 0000000..699a3b9 --- /dev/null +++ b/tools/Module.mk @@ -0,0 +1,84 @@ +# I2C tools for Linux +# +# Copyright (C) 2007 Jean Delvare <khali@linux-fr.org> +# +# Licensed under the GNU General Public License. + +TOOLS_DIR := tools + +TOOLS_CFLAGS := -Wstrict-prototypes -Wshadow -Wpointer-arith -Wcast-qual \ + -Wcast-align -Wwrite-strings -Wnested-externs -Winline \ + -W -Wundef -Wmissing-prototypes -Iinclude + +TOOLS_TARGETS := i2cdetect i2cdump i2cset i2cget + +# +# Programs +# + +$(TOOLS_DIR)/i2cdetect: $(TOOLS_DIR)/i2cdetect.o $(TOOLS_DIR)/i2cbusses.o + $(CC) $(LDFLAGS) -o $@ $^ + +$(TOOLS_DIR)/i2cdump: $(TOOLS_DIR)/i2cdump.o $(TOOLS_DIR)/i2cbusses.o $(TOOLS_DIR)/util.o + $(CC) $(LDFLAGS) -o $@ $^ + +$(TOOLS_DIR)/i2cset: $(TOOLS_DIR)/i2cset.o $(TOOLS_DIR)/i2cbusses.o $(TOOLS_DIR)/util.o + $(CC) $(LDFLAGS) -o $@ $^ + +$(TOOLS_DIR)/i2cget: $(TOOLS_DIR)/i2cget.o $(TOOLS_DIR)/i2cbusses.o $(TOOLS_DIR)/util.o + $(CC) $(LDFLAGS) -o $@ $^ + +# +# Objects +# + +$(TOOLS_DIR)/i2cdetect.o: $(TOOLS_DIR)/i2cdetect.c $(TOOLS_DIR)/i2cbusses.h + $(CC) $(CFLAGS) $(TOOLS_CFLAGS) -c $< -o $@ + +$(TOOLS_DIR)/i2cdump.o: $(TOOLS_DIR)/i2cdump.c $(TOOLS_DIR)/i2cbusses.h $(TOOLS_DIR)/util.h + $(CC) $(CFLAGS) $(TOOLS_CFLAGS) -c $< -o $@ + +$(TOOLS_DIR)/i2cset.o: $(TOOLS_DIR)/i2cset.c $(TOOLS_DIR)/i2cbusses.h $(TOOLS_DIR)/util.h + $(CC) $(CFLAGS) $(TOOLS_CFLAGS) -c $< -o $@ + +$(TOOLS_DIR)/i2cget.o: $(TOOLS_DIR)/i2cget.c $(TOOLS_DIR)/i2cbusses.h $(TOOLS_DIR)/util.h + $(CC) $(CFLAGS) $(TOOLS_CFLAGS) -c $< -o $@ + +$(TOOLS_DIR)/i2cbusses.o: $(TOOLS_DIR)/i2cbusses.c $(TOOLS_DIR)/i2cbusses.h + $(CC) $(CFLAGS) $(TOOLS_CFLAGS) -c $< -o $@ + +$(TOOLS_DIR)/util.o: $(TOOLS_DIR)/util.c $(TOOLS_DIR)/util.h + $(CC) $(CFLAGS) $(TOOLS_CFLAGS) -c $< -o $@ + +# +# Commands +# + +all-tools: $(addprefix $(TOOLS_DIR)/,$(TOOLS_TARGETS)) + +strip-tools: $(addprefix $(TOOLS_DIR)/,$(TOOLS_TARGETS)) + strip $(addprefix $(TOOLS_DIR)/,$(TOOLS_TARGETS)) + +clean-tools: + $(RM) $(addprefix $(TOOLS_DIR)/,*.o $(TOOLS_TARGETS)) + +install-tools: $(addprefix $(TOOLS_DIR)/,$(TOOLS_TARGETS)) + $(INSTALL_DIR) $(DESTDIR)$(sbindir) $(DESTDIR)$(man8dir) + for program in $(TOOLS_TARGETS) ; do \ + $(INSTALL_PROGRAM) $(TOOLS_DIR)/$$program $(DESTDIR)$(sbindir) ; \ + $(INSTALL_DATA) $(TOOLS_DIR)/$$program.8 $(DESTDIR)$(man8dir) ; done + +uninstall-tools: + for program in $(TOOLS_TARGETS) ; do \ + $(RM) $(DESTDIR)$(sbindir)/$$program ; \ + $(RM) $(DESTDIR)$(man8dir)/$$program.8 ; done + +all: all-tools + +strip: strip-tools + +clean: clean-tools + +install: install-tools + +uninstall: uninstall-tools diff --git a/tools/i2cbusses.c b/tools/i2cbusses.c new file mode 100644 index 0000000..693c191 --- /dev/null +++ b/tools/i2cbusses.c @@ -0,0 +1,414 @@ +/* + i2cbusses: Print the installed i2c busses for both 2.4 and 2.6 kernels. + Part of user-space programs to access for I2C + devices. + Copyright (c) 1999-2003 Frodo Looijaard <frodol@dds.nl> and + Mark D. Studebaker <mdsxyz123@yahoo.com> + Copyright (C) 2008 Jean Delvare <khali@linux-fr.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02110-1301 USA. +*/ + +/* For strdup */ +#define _BSD_SOURCE 1 + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/param.h> /* for NAME_MAX */ +#include <string.h> +#include <strings.h> /* for strcasecmp() */ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <limits.h> +#include <dirent.h> +#include <fcntl.h> +#include <errno.h> +#include "i2cbusses.h" +#include <linux/i2c-dev.h> + +enum adt { adt_dummy, adt_isa, adt_i2c, adt_smbus, adt_unknown }; + +struct adap_type { + const char *funcs; + const char* algo; +}; + +static struct adap_type adap_types[5] = { + { .funcs = "dummy", + .algo = "Dummy bus", }, + { .funcs = "isa", + .algo = "ISA bus", }, + { .funcs = "i2c", + .algo = "I2C adapter", }, + { .funcs = "smbus", + .algo = "SMBus adapter", }, + { .funcs = "unknown", + .algo = "N/A", }, +}; + +static enum adt i2c_get_funcs(int i2cbus) +{ + unsigned long funcs; + int file; + char filename[20]; + enum adt ret; + + file = open_i2c_dev(i2cbus, filename, 1); + if (file < 0) + return adt_unknown; + + if (ioctl(file, I2C_FUNCS, &funcs) < 0) + ret = adt_unknown; + else if (funcs & I2C_FUNC_I2C) + ret = adt_i2c; + else if (funcs & (I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA)) + ret = adt_smbus; + else + ret = adt_dummy; + + close(file); + return ret; +} + +/* Remove trailing spaces from a string + Return the new string length including the trailing NUL */ +static int rtrim(char *s) +{ + int i; + + for (i = strlen(s) - 1; i >= 0 && (s[i] == ' ' || s[i] == '\n'); i--) + s[i] = '\0'; + return i + 2; +} + +void free_adapters(struct i2c_adap *adapters) +{ + int i; + + for (i = 0; adapters[i].name; i++) + free(adapters[i].name); + free(adapters); +} + +/* We allocate space for the adapters in bunches. The last item is a + terminator, so here we start with room for 7 adapters, which should + be enough in most cases. If not, we allocate more later as needed. */ +#define BUNCH 8 + +/* n must match the size of adapters at calling time */ +static struct i2c_adap *more_adapters(struct i2c_adap *adapters, int n) +{ + struct i2c_adap *new_adapters; + + new_adapters = realloc(adapters, (n + BUNCH) * sizeof(struct i2c_adap)); + if (!new_adapters) { + free_adapters(adapters); + return NULL; + } + memset(new_adapters + n, 0, BUNCH * sizeof(struct i2c_adap)); + + return new_adapters; +} + +struct i2c_adap *gather_i2c_busses(void) +{ + char s[120]; + struct dirent *de, *dde; + DIR *dir, *ddir; + FILE *f; + char fstype[NAME_MAX], sysfs[NAME_MAX], n[NAME_MAX]; + int foundsysfs = 0; + int count=0; + struct i2c_adap *adapters; + + adapters = calloc(BUNCH, sizeof(struct i2c_adap)); + if (!adapters) + return NULL; + + /* look in /proc/bus/i2c */ + if ((f = fopen("/proc/bus/i2c", "r"))) { + while (fgets(s, 120, f)) { + char *algo, *name, *type, *all; + int len_algo, len_name, len_type; + int i2cbus; + + algo = strrchr(s, '\t'); + *(algo++) = '\0'; + len_algo = rtrim(algo); + + name = strrchr(s, '\t'); + *(name++) = '\0'; + len_name = rtrim(name); + + type = strrchr(s, '\t'); + *(type++) = '\0'; + len_type = rtrim(type); + + sscanf(s, "i2c-%d", &i2cbus); + + if ((count + 1) % BUNCH == 0) { + /* We need more space */ + adapters = more_adapters(adapters, count + 1); + if (!adapters) + return NULL; + } + + all = malloc(len_name + len_type + len_algo); + if (all == NULL) { + free_adapters(adapters); + return NULL; + } + adapters[count].nr = i2cbus; + adapters[count].name = strcpy(all, name); + adapters[count].funcs = strcpy(all + len_name, type); + adapters[count].algo = strcpy(all + len_name + len_type, + algo); + count++; + } + fclose(f); + goto done; + } + + /* look in sysfs */ + /* First figure out where sysfs was mounted */ + if ((f = fopen("/proc/mounts", "r")) == NULL) { + goto done; + } + while (fgets(n, NAME_MAX, f)) { + sscanf(n, "%*[^ ] %[^ ] %[^ ] %*s\n", sysfs, fstype); + if (strcasecmp(fstype, "sysfs") == 0) { + foundsysfs++; + break; + } + } + fclose(f); + if (! foundsysfs) { + goto done; + } + + /* Bus numbers in i2c-adapter don't necessarily match those in + i2c-dev and what we really care about are the i2c-dev numbers. + Unfortunately the names are harder to get in i2c-dev */ + strcat(sysfs, "/class/i2c-dev"); + if(!(dir = opendir(sysfs))) + goto done; + /* go through the busses */ + while ((de = readdir(dir)) != NULL) { + if (!strcmp(de->d_name, ".")) + continue; + if (!strcmp(de->d_name, "..")) + continue; + + /* this should work for kernels 2.6.5 or higher and */ + /* is preferred because is unambiguous */ + sprintf(n, "%s/%s/name", sysfs, de->d_name); + f = fopen(n, "r"); + /* this seems to work for ISA */ + if(f == NULL) { + sprintf(n, "%s/%s/device/name", sysfs, de->d_name); + f = fopen(n, "r"); + } + /* non-ISA is much harder */ + /* and this won't find the correct bus name if a driver + has more than one bus */ + if(f == NULL) { + sprintf(n, "%s/%s/device", sysfs, de->d_name); + if(!(ddir = opendir(n))) + continue; + while ((dde = readdir(ddir)) != NULL) { + if (!strcmp(dde->d_name, ".")) + continue; + if (!strcmp(dde->d_name, "..")) + continue; + if ((!strncmp(dde->d_name, "i2c-", 4))) { + sprintf(n, "%s/%s/device/%s/name", + sysfs, de->d_name, dde->d_name); + if((f = fopen(n, "r"))) + goto found; + } + } + } + +found: + if (f != NULL) { + int i2cbus; + enum adt type; + char *px; + + px = fgets(s, 120, f); + fclose(f); + if (!px) { + fprintf(stderr, "%s: read error\n", n); + continue; + } + if ((px = strchr(s, '\n')) != NULL) + *px = 0; + if (!sscanf(de->d_name, "i2c-%d", &i2cbus)) + continue; + if (!strncmp(s, "ISA ", 4)) { + type = adt_isa; + } else { + /* Attempt to probe for adapter capabilities */ + type = i2c_get_funcs(i2cbus); + } + + if ((count + 1) % BUNCH == 0) { + /* We need more space */ + adapters = more_adapters(adapters, count + 1); + if (!adapters) + return NULL; + } + + adapters[count].nr = i2cbus; + adapters[count].name = strdup(s); + if (adapters[count].name == NULL) { + free_adapters(adapters); + return NULL; + } + adapters[count].funcs = adap_types[type].funcs; + adapters[count].algo = adap_types[type].algo; + count++; + } + } + closedir(dir); + +done: + return adapters; +} + +static int lookup_i2c_bus_by_name(const char *bus_name) +{ + struct i2c_adap *adapters; + int i, i2cbus = -1; + + adapters = gather_i2c_busses(); + if (adapters == NULL) { + fprintf(stderr, "Error: Out of memory!\n"); + return -3; + } + + /* Walk the list of i2c busses, looking for the one with the + right name */ + for (i = 0; adapters[i].name; i++) { + if (strcmp(adapters[i].name, bus_name) == 0) { + if (i2cbus >= 0) { + fprintf(stderr, + "Error: I2C bus name is not unique!\n"); + i2cbus = -4; + goto done; + } + i2cbus = adapters[i].nr; + } + } + + if (i2cbus == -1) + fprintf(stderr, "Error: I2C bus name doesn't match any " + "bus present!\n"); + +done: + free_adapters(adapters); + return i2cbus; +} + +/* + * Parse an I2CBUS command line argument and return the corresponding + * bus number, or a negative value if the bus is invalid. + */ +int lookup_i2c_bus(const char *i2cbus_arg) +{ + long i2cbus; + char *end; + + i2cbus = strtol(i2cbus_arg, &end, 0); + if (*end || !*i2cbus_arg) { + /* Not a number, maybe a name? */ + return lookup_i2c_bus_by_name(i2cbus_arg); + } + if (i2cbus < 0 || i2cbus > 0xff) { + fprintf(stderr, "Error: I2C bus out of range (0-255)!\n"); + return -2; + } + + return i2cbus; +} + +/* + * Parse a CHIP-ADDRESS command line argument and return the corresponding + * chip address, or a negative value if the address is invalid. + */ +int parse_i2c_address(const char *address_arg) +{ + long address; + char *end; + + address = strtol(address_arg, &end, 0); + if (*end || !*address_arg) { + fprintf(stderr, "Error: Chip address is not a number!\n"); + return -1; + } + if (address < 0x03 || address > 0x77) { + fprintf(stderr, "Error: Chip address out of range " + "(0x03-0x77)!\n"); + return -2; + } + + return address; +} + +int open_i2c_dev(const int i2cbus, char *filename, const int quiet) +{ + int file; + + sprintf(filename, "/dev/i2c/%d", i2cbus); + file = open(filename, O_RDWR); + + if (file < 0 && errno == ENOENT) { + sprintf(filename, "/dev/i2c-%d", i2cbus); + file = open(filename, O_RDWR); + } + + if (file < 0 && !quiet) { + if (errno == ENOENT) { + fprintf(stderr, "Error: Could not open file " + "`/dev/i2c-%d' or `/dev/i2c/%d': %s\n", + i2cbus, i2cbus, strerror(ENOENT)); + } else { + fprintf(stderr, "Error: Could not open file " + "`%s': %s\n", filename, strerror(errno)); + if (errno == EACCES) + fprintf(stderr, "Run as root?\n"); + } + } + + return file; +} + +int set_slave_addr(int file, int address, int force) +{ + /* With force, let the user read from/write to the registers + even when a driver is also running */ + if (ioctl(file, force ? I2C_SLAVE_FORCE : I2C_SLAVE, address) < 0) { + fprintf(stderr, + "Error: Could not set address to 0x%02x: %s\n", + address, strerror(errno)); + return -errno; + } + + return 0; +} diff --git a/tools/i2cbusses.h b/tools/i2cbusses.h new file mode 100644 index 0000000..b089887 --- /dev/null +++ b/tools/i2cbusses.h @@ -0,0 +1,42 @@ +/* + i2cbusses.h - Part of the i2c-tools package + + Copyright (C) 2004-2007 Jean Delvare <khali@linux-fr.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02110-1301 USA. +*/ + +#ifndef _I2CBUSSES_H +#define _I2CBUSSES_H + +struct i2c_adap { + int nr; + char *name; + const char *funcs; + const char *algo; +}; + +struct i2c_adap *gather_i2c_busses(void); +void free_adapters(struct i2c_adap *adapters); + +int lookup_i2c_bus(const char *i2cbus_arg); +int parse_i2c_address(const char *address_arg); +int open_i2c_dev(const int i2cbus, char *filename, const int quiet); +int set_slave_addr(int file, int address, int force); + +#define MISSING_FUNC_FMT "Error: Adapter does not have %s capability\n" + +#endif diff --git a/tools/i2cdetect.8 b/tools/i2cdetect.8 new file mode 100644 index 0000000..47cad04 --- /dev/null +++ b/tools/i2cdetect.8 @@ -0,0 +1,87 @@ +.TH I2CDETECT 8 "April 2008" +.SH NAME +i2cdetect \- detect I2C chips + +.SH SYNOPSIS +.B i2cdetect +.RI [ -y ] +.RI [ -a ] +.RI [ -q | -r ] +.I i2cbus +.RI [ "first last" ] +.br +.B i2cdetect +.I -F +.I i2cbus +.br +.B i2cdetect +.I -V +.br +.B i2cdetect +.I -l + +.SH DESCRIPTION +i2cdetect is a userspace program to scan an I2C bus for devices. It +outputs a table with the list of detected devices on the specified bus. +\fIi2cbus\fR indicates the number or name of the I2C bus to be scanned, and +should correspond to one of the busses listed by \fIi2cdetect -l\fR. +The optional parameters \fIfirst\fR and \fIlast\fR restrict the scanning +range (default: from 0x03 to 0x77). +.PP +i2cdetect can also be used to query the functionalities of an I2C bus +(see option \fB-F\fP.) + +.SH WARNING +This program can confuse your I2C bus, cause data loss and worse! + +.SH INTERPRETING THE OUTPUT +Each cell in the output table will contain one of the following symbols: +.IP \(bu "\w'\(bu'u+1n" +"--". The address was probed but no chip answered. +.IP \(bu +"UU". Probing was skipped, because this address is currently in use by +a driver. This strongly suggests that there is a chip at this address. +.IP \(bu +An address number in hexadecimal, e.g. "2d" or "4e". A chip +was found at this address. + +.SH OPTIONS +.TP +.B "\-y" +Disable interactive mode. By default, i2cdetect will wait for a confirmation +from the user before messing with the I2C bus. When this flag is used, it +will perform the operation directly. This is mainly meant to be used in +scripts. +.TP +.B "\-a" +Force scanning of non-regular addresses. Not recommended. +.TP +.B "\-q" +Use SMBus "quick write" commands for probing (by default, the command +used is the one believed to be the safest for each address). +Not recommended. This is known to corrupt the Atmel AT24RF08 EEPROM +found on many IBM Thinkpad laptops. +.TP +.B "\-r" +Use SMBus "read byte" commands for probing (by default, the command +used is the one believed to be the safest for each address). +Not recommended. This is known to lock SMBus on various write-only +chips (most notably clock chips at address 0x69). +.TP +.B "\-F" +Display the list of functionalities implemented by the adapter and exit. +.TP +.B "\-V" +Display the version and exit. +.TP +.B "\-l" +Output a list of installed busses. + +.SH SEE ALSO +i2cdump(8), sensors-detect(8) + +.SH AUTHOR +Frodo Looijaard, Mark D. Studebaker and Jean Delvare + +This manual page was originally written by Aurelien Jarno +<aurel32@debian.org>, for the Debian GNU/Linux system. diff --git a/tools/i2cdetect.c b/tools/i2cdetect.c new file mode 100644 index 0000000..4750db5 --- /dev/null +++ b/tools/i2cdetect.c @@ -0,0 +1,357 @@ +/* + i2cdetect.c - a user-space program to scan for I2C devices + Copyright (C) 1999-2004 Frodo Looijaard <frodol@dds.nl>, + Mark D. Studebaker <mdsxyz123@yahoo.com> and + Jean Delvare <khali@linux-fr.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02110-1301 USA. +*/ + +#include <errno.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <linux/i2c-dev.h> +#include "i2cbusses.h" +#include "../version.h" + +#define MODE_AUTO 0 +#define MODE_QUICK 1 +#define MODE_READ 2 +#define MODE_FUNC 3 + +static void help(void) +{ + fprintf(stderr, + "Usage: i2cdetect [-y] [-a] [-q|-r] I2CBUS [FIRST LAST]\n" + " i2cdetect -F I2CBUS\n" + " i2cdetect -l\n" + " I2CBUS is an integer or an I2C bus name\n" + " If provided, FIRST and LAST limit the probing range.\n"); +} + +static int scan_i2c_bus(int file, int mode, int first, int last) +{ + int i, j; + int res; + + printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\n"); + + for (i = 0; i < 128; i += 16) { + printf("%02x: ", i); + for(j = 0; j < 16; j++) { + fflush(stdout); + + /* Skip unwanted addresses */ + if (i+j < first || i+j > last) { + printf(" "); + continue; + } + + /* Set slave address */ + if (ioctl(file, I2C_SLAVE, i+j) < 0) { + if (errno == EBUSY) { + printf("UU "); + continue; + } else { + fprintf(stderr, "Error: Could not set " + "address to 0x%02x: %s\n", i+j, + strerror(errno)); + return -1; + } + } + + /* Probe this address */ + switch (mode) { + case MODE_QUICK: + /* This is known to corrupt the Atmel AT24RF08 + EEPROM */ + res = i2c_smbus_write_quick(file, + I2C_SMBUS_WRITE); + break; + case MODE_READ: + /* This is known to lock SMBus on various + write-only chips (mainly clock chips) */ + res = i2c_smbus_read_byte(file); + break; + default: + if ((i+j >= 0x30 && i+j <= 0x37) + || (i+j >= 0x50 && i+j <= 0x5F)) + res = i2c_smbus_read_byte(file); + else + res = i2c_smbus_write_quick(file, + I2C_SMBUS_WRITE); + } + + if (res < 0) + printf("-- "); + else + printf("%02x ", i+j); + } + printf("\n"); + } + + return 0; +} + +struct func +{ + long value; + const char* name; +}; + +static const struct func all_func[] = { + { .value = I2C_FUNC_I2C, + .name = "I2C" }, + { .value = I2C_FUNC_SMBUS_QUICK, + .name = "SMBus Quick Command" }, + { .value = I2C_FUNC_SMBUS_WRITE_BYTE, + .name = "SMBus Send Byte" }, + { .value = I2C_FUNC_SMBUS_READ_BYTE, + .name = "SMBus Receive Byte" }, + { .value = I2C_FUNC_SMBUS_WRITE_BYTE_DATA, + .name = "SMBus Write Byte" }, + { .value = I2C_FUNC_SMBUS_READ_BYTE_DATA, + .name = "SMBus Read Byte" }, + { .value = I2C_FUNC_SMBUS_WRITE_WORD_DATA, + .name = "SMBus Write Word" }, + { .value = I2C_FUNC_SMBUS_READ_WORD_DATA, + .name = "SMBus Read Word" }, + { .value = I2C_FUNC_SMBUS_PROC_CALL, + .name = "SMBus Process Call" }, + { .value = I2C_FUNC_SMBUS_WRITE_BLOCK_DATA, + .name = "SMBus Block Write" }, + { .value = I2C_FUNC_SMBUS_READ_BLOCK_DATA, + .name = "SMBus Block Read" }, + { .value = I2C_FUNC_SMBUS_BLOCK_PROC_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" }, + { .value = I2C_FUNC_SMBUS_READ_I2C_BLOCK, + .name = "I2C Block Read" }, + { .value = 0, .name = "" } +}; + +static void print_functionality(unsigned long funcs) +{ + int i; + + for (i = 0; all_func[i].value; i++) { + printf("%-32s %s\n", all_func[i].name, + (funcs & all_func[i].value) ? "yes" : "no"); + } +} + +/* + * Print the installed i2c busses. The format is those of Linux 2.4's + * /proc/bus/i2c for historical compatibility reasons. + */ +static void print_i2c_busses(void) +{ + struct i2c_adap *adapters; + int count; + + adapters = gather_i2c_busses(); + if (adapters == NULL) { + fprintf(stderr, "Error: Out of memory!\n"); + return; + } + + for (count = 0; adapters[count].name; count++) { + printf("i2c-%d\t%-10s\t%-32s\t%s\n", + adapters[count].nr, adapters[count].funcs, + adapters[count].name, adapters[count].algo); + } + + free_adapters(adapters); +} + +int main(int argc, char *argv[]) +{ + char *end; + int i2cbus, file, res; + char filename[20]; + unsigned long funcs; + int mode = MODE_AUTO; + int first = 0x03, last = 0x77; + int flags = 0; + int yes = 0, version = 0, list = 0; + + /* handle (optional) flags first */ + while (1+flags < argc && argv[1+flags][0] == '-') { + switch (argv[1+flags][1]) { + case 'V': version = 1; break; + case 'y': yes = 1; break; + case 'l': list = 1; break; + case 'F': + if (mode != MODE_AUTO && mode != MODE_FUNC) { + fprintf(stderr, "Error: Different modes " + "specified!\n"); + exit(1); + } + mode = MODE_FUNC; + break; + case 'r': + if (mode == MODE_QUICK) { + fprintf(stderr, "Error: Different modes " + "specified!\n"); + exit(1); + } + mode = MODE_READ; + break; + case 'q': + if (mode == MODE_READ) { + fprintf(stderr, "Error: Different modes " + "specified!\n"); + exit(1); + } + mode = MODE_QUICK; + break; + case 'a': + first = 0x00; + last = 0x7F; + break; + default: + fprintf(stderr, "Error: Unsupported option " + "\"%s\"!\n", argv[1+flags]); + help(); + exit(1); + } + flags++; + } + + if (version) { + fprintf(stderr, "i2cdetect version %s\n", VERSION); + exit(0); + } + + if (list) { + print_i2c_busses(); + exit(0); + } + + if (argc < flags + 2) { + fprintf(stderr, "Error: No i2c-bus specified!\n"); + help(); + exit(1); + } + i2cbus = lookup_i2c_bus(argv[flags+1]); + if (i2cbus < 0) { + help(); + exit(1); + } + + /* read address range if present */ + if (argc == flags + 4 && mode != MODE_FUNC) { + int tmp; + + tmp = strtol(argv[flags+2], &end, 0); + if (*end) { + fprintf(stderr, "Error: FIRST argment not a " + "number!\n"); + help(); + exit(1); + } + if (tmp < first || tmp > last) { + fprintf(stderr, "Error: FIRST argument out of range " + "(0x%02x-0x%02x)!\n", first, last); + help(); + exit(1); + } + first = tmp; + + tmp = strtol(argv[flags+3], &end, 0); + if (*end) { + fprintf(stderr, "Error: LAST argment not a " + "number!\n"); + help(); + exit(1); + } + if (tmp < first || tmp > last) { + fprintf(stderr, "Error: LAST argument out of range " + "(0x%02x-0x%02x)!\n", first, last); + help(); + exit(1); + } + last = tmp; + } else if (argc != flags + 2) { + help(); + exit(1); + } + + file = open_i2c_dev(i2cbus, filename, 0); + if (file < 0) { + exit(1); + } + + if (ioctl(file, I2C_FUNCS, &funcs) < 0) { + fprintf(stderr, "Error: Could not get the adapter " + "functionality matrix: %s\n", strerror(errno)); + close(file); + exit(1); + } + + /* Special case, we only list the implemented functionalities */ + if (mode == MODE_FUNC) { + close(file); + printf("Functionalities implemented by %s:\n", filename); + print_functionality(funcs); + exit(0); + } + + if (mode != MODE_READ && !(funcs & I2C_FUNC_SMBUS_QUICK)) { + fprintf(stderr, "Error: Can't use SMBus Quick Write command " + "on this bus (ISA bus?)\n"); + close(file); + exit(1); + } + if (mode != MODE_QUICK && !(funcs & I2C_FUNC_SMBUS_READ_BYTE)) { + fprintf(stderr, "Error: Can't use SMBus Read Byte command " + "on this bus (ISA bus?)\n"); + close(file); + exit(1); + } + + if (!yes) { + char s[2]; + + fprintf(stderr, "WARNING! This program can confuse your I2C " + "bus, cause data loss and worse!\n"); + + fprintf(stderr, "I will probe file %s%s.\n", filename, + mode==MODE_QUICK?" using quick write commands": + mode==MODE_READ?" using read byte commands":""); + fprintf(stderr, "I will probe address range 0x%02x-0x%02x.\n", + first, last); + + fprintf(stderr, "Continue? [Y/n] "); + fflush(stderr); + if (!fgets(s, 2, stdin) + || (s[0] != '\n' && s[0] != 'y' && s[0] != 'Y')) { + fprintf(stderr, "Aborting on user request.\n"); + exit(0); + } + } + + res = scan_i2c_bus(file, mode, first, last); + + close(file); + + exit(res?1:0); +} diff --git a/tools/i2cdump.8 b/tools/i2cdump.8 new file mode 100644 index 0000000..7a02a47 --- /dev/null +++ b/tools/i2cdump.8 @@ -0,0 +1,84 @@ +.TH I2CDUMP 8 "May 2008" +.SH NAME +i2cdump \- examine I2C registers + +.SH SYNOPSIS +.B i2cdump +.RB [ -f ] +.RB [ "-r first-last" ] +.RB [ -y ] +.I i2cbus +.I address +.RI [ "mode " [ "bank " [ bankreg ]]] +.br +.B i2cdump +.B -V + +.SH DESCRIPTION +i2cdump is a small helper program to examine registers +visible through the I2C bus. + +.SH OPTIONS +.TP +.B -V +Display the version and exit. +.TP +.B -f +Force access to the device even if it is already busy. By default, i2cdump +will refuse to access a device which is already under the control of a +kernel driver. Using this flag is dangerous, it can seriously confuse the +kernel driver in question. It can also cause i2cdump to return invalid +results. So use at your own risk and only if you know what you're doing. +.TP +.B -r first-last +Limit the range of registers being accessed. This option is only available +with modes \fBb\fP, \fBw\fP, \fBc\fP and \fBW\fP. For mode \fBW\fP, +\fBfirst\fR must be even and \fBlast\fR must be odd. +.TP +.B -y +Disable interactive mode. By default, i2cdump will wait for a confirmation +from the user before messing with the I2C bus. When this flag is used, it +will perform the operation directly. This is mainly meant to be used in +scripts. +.PP +At least two options must be provided to i2cdump. \fIi2cbus\fR indicates the +number or name of the I2C bus to be scanned. This number should correspond to one +of the busses listed by \fIi2cdetect -l\fR. \fIaddress\fR indicates the +address to be scanned on that bus, and is an integer between 0x03 and 0x77. +.PP +The \fImode\fR parameter, if specified, is one of the letters \fBb\fP, \fBw\fP, +\fBs\fP, or \fBi\fP, corresponding to a read size of a single byte, a 16-bit +word, an SMBus block, an I2C block, respectively. The \fBc\fP mode is a +little different, it reads all bytes consecutively, and is useful for chips that +have an address auto-increment feature, such as EEPROMs. The \fBW\fP mode is +also special, it is similar to \fBw\fP except that a read command will only +be issued on even register addresses; this is again mainly useful for EEPROMs. +.PP +A \fBp\fP can also be appended to the \fImode\fR parameter (except for +\fBi\fP and \fBW\fP) to enable PEC. If the \fImode\fR parameter is omitted, +i2cdump defaults to byte access without PEC. +.PP +The \fIbank\fR and \fIbankreg\fR parameters are useful on the W83781D and +similar chips (at the time of writing, all Winbond and Asus chips). +\fIbank\fR is an integer between 0 and 7, and \fIbankreg\fR is an integer +between 0x00 and 0xFF (default value: 0x4E). The W83781D data sheet has more +information on bank selection. + +.SH WARNING +i2cdump can be dangerous if used improperly. Most notably, the \fBc\fP mode +starts with WRITING a byte to the chip. On most chips it will be stored in the +address pointer register, which is OK, but some chips with a single register +or no (visible) register at all will most likely see this as a real WRITE, +resulting in possible misbehavior or corruption. Do not use i2cdump +on random addresses. Anyway, it is of little use unless you have good +knowledge of the chip you're working with and an idea of what you are looking +for. + +.SH SEE ALSO +i2cset(8), i2cdetect(8), isadump(8) + +.SH AUTHOR +Frodo Looijaard, Mark D. Studebaker and Jean Delvare + +This manual page was originally written by David Z Maze <dmaze@debian.org> for +the Debian GNU/Linux system. diff --git a/tools/i2cdump.c b/tools/i2cdump.c new file mode 100644 index 0000000..84b4bca --- /dev/null +++ b/tools/i2cdump.c @@ -0,0 +1,487 @@ +/* + i2cdump.c - a user-space program to dump I2C registers + Copyright (C) 2002-2003 Frodo Looijaard <frodol@dds.nl>, and + Mark D. Studebaker <mdsxyz123@yahoo.com> + Copyright (C) 2004-2008 Jean Delvare <khali@linux-fr.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02110-1301 USA. +*/ + +#include <errno.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <linux/i2c-dev.h> +#include "i2cbusses.h" +#include "util.h" +#include "../version.h" + +static void help(void) +{ + fprintf(stderr, + "Usage: i2cdump [-f] [-y] [-r first-last] I2CBUS ADDRESS [MODE [BANK [BANKREG]]]\n" + " I2CBUS is an integer or an I2C bus name\n" + " ADDRESS is an integer (0x03 - 0x77)\n" + " MODE is one of:\n" + " b (byte, default)\n" + " w (word)\n" + " W (word on even register addresses)\n" + " s (SMBus block)\n" + " i (I2C block)\n" + " c (consecutive byte)\n" + " Append p for SMBus PEC\n"); +} + +static int check_funcs(int file, int size, int pec) +{ + unsigned long funcs; + + /* check adapter functionality */ + if (ioctl(file, I2C_FUNCS, &funcs) < 0) { + fprintf(stderr, "Error: Could not get the adapter " + "functionality matrix: %s\n", strerror(errno)); + return -1; + } + + switch(size) { + case I2C_SMBUS_BYTE: + if (!(funcs & I2C_FUNC_SMBUS_READ_BYTE)) { + fprintf(stderr, MISSING_FUNC_FMT, "SMBus receive byte"); + return -1; + } + if (!(funcs & I2C_FUNC_SMBUS_WRITE_BYTE)) { + fprintf(stderr, MISSING_FUNC_FMT, "SMBus send byte"); + return -1; + } + break; + + case I2C_SMBUS_BYTE_DATA: + if (!(funcs & I2C_FUNC_SMBUS_READ_BYTE_DATA)) { + fprintf(stderr, MISSING_FUNC_FMT, "SMBus read byte"); + return -1; + } + break; + + case I2C_SMBUS_WORD_DATA: + if (!(funcs & I2C_FUNC_SMBUS_READ_WORD_DATA)) { + fprintf(stderr, MISSING_FUNC_FMT, "SMBus read word"); + return -1; + } + break; + + case I2C_SMBUS_BLOCK_DATA: + if (!(funcs & I2C_FUNC_SMBUS_READ_BLOCK_DATA)) { + fprintf(stderr, MISSING_FUNC_FMT, "SMBus block read"); + return -1; + } + break; + + case I2C_SMBUS_I2C_BLOCK_DATA: + if (!(funcs & I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { + fprintf(stderr, MISSING_FUNC_FMT, "I2C block read"); + return -1; + } + break; + } + + if (pec + && !(funcs & (I2C_FUNC_SMBUS_PEC | I2C_FUNC_I2C))) { + fprintf(stderr, "Warning: Adapter does " + "not seem to support PEC\n"); + } + + return 0; +} + +int main(int argc, char *argv[]) +{ + char *end; + int i, j, res, i2cbus, address, size, file; + int bank = 0, bankreg = 0x4E, old_bank = 0; + char filename[20]; + int block[256], s_length = 0; + int pec = 0, even = 0; + int flags = 0; + int force = 0, yes = 0, version = 0; + const char *range = NULL; + int first = 0x00, last = 0xff; + + /* handle (optional) flags first */ + while (1+flags < argc && argv[1+flags][0] == '-') { + switch (argv[1+flags][1]) { + case 'V': version = 1; break; + case 'f': force = 1; break; + case 'r': range = argv[1+(++flags)]; break; + case 'y': yes = 1; break; + default: + fprintf(stderr, "Error: Unsupported option " + "\"%s\"!\n", argv[1+flags]); + help(); + exit(1); + } + flags++; + } + + if (version) { + fprintf(stderr, "i2cdump version %s\n", VERSION); + exit(0); + } + + if (argc < flags + 2) { + fprintf(stderr, "Error: No i2c-bus specified!\n"); + help(); + exit(1); + } + i2cbus = lookup_i2c_bus(argv[flags+1]); + if (i2cbus < 0) { + help(); + exit(1); + } + + if (argc < flags + 3) { + fprintf(stderr, "Error: No address specified!\n"); + help(); + exit(1); + } + address = parse_i2c_address(argv[flags+2]); + if (address < 0) { + help(); + exit(1); + } + + if (argc < flags + 4) { + fprintf(stderr, "No size specified (using byte-data access)\n"); + size = I2C_SMBUS_BYTE_DATA; + } else if (!strncmp(argv[flags+3], "b", 1)) { + size = I2C_SMBUS_BYTE_DATA; + pec = argv[flags+3][1] == 'p'; + } else if (!strncmp(argv[flags+3], "w", 1)) { + size = I2C_SMBUS_WORD_DATA; + pec = argv[flags+3][1] == 'p'; + } else if (!strncmp(argv[flags+3], "W", 1)) { + size = I2C_SMBUS_WORD_DATA; + even = 1; + } else if (!strncmp(argv[flags+3], "s", 1)) { + size = I2C_SMBUS_BLOCK_DATA; + pec = argv[flags+3][1] == 'p'; + } else if (!strncmp(argv[flags+3], "c", 1)) { + size = I2C_SMBUS_BYTE; + pec = argv[flags+3][1] == 'p'; + } else if (!strcmp(argv[flags+3], "i")) + size = I2C_SMBUS_I2C_BLOCK_DATA; + else { + fprintf(stderr, "Error: Invalid mode!\n"); + help(); + exit(1); + } + + if (argc > flags + 4) { + bank = strtol(argv[flags+4], &end, 0); + if (*end || size == I2C_SMBUS_I2C_BLOCK_DATA) { + fprintf(stderr, "Error: Invalid bank number!\n"); + help(); + exit(1); + } + if ((size == I2C_SMBUS_BYTE_DATA || size == I2C_SMBUS_WORD_DATA) + && (bank < 0 || bank > 15)) { + fprintf(stderr, "Error: bank out of range!\n"); + help(); + exit(1); + } + if (size == I2C_SMBUS_BLOCK_DATA + && (bank < 0 || bank > 0xff)) { + fprintf(stderr, "Error: block command out of range!\n"); + help(); + exit(1); + } + + if (argc > flags + 5) { + bankreg = strtol(argv[flags+5], &end, 0); + if (*end || size == I2C_SMBUS_BLOCK_DATA) { + fprintf(stderr, "Error: Invalid bank register " + "number!\n"); + help(); + exit(1); + } + if (bankreg < 0 || bankreg > 0xff) { + fprintf(stderr, "Error: bank out of range " + "(0-0xff)!\n"); + help(); + exit(1); + } + } + } + + /* Parse optional range string */ + if (range) { + char *dash; + + first = strtol(range, &dash, 0); + if (dash == range || *dash != '-' + || first < 0 || first > 0xff) { + fprintf(stderr, "Error: Invalid range parameter!\n"); + exit(1); + } + last = strtol(++dash, &end, 0); + if (end == dash || *end != '\0' + || last < first || last > 0xff) { + fprintf(stderr, "Error: Invalid range parameter!\n"); + exit(1); + } + + /* Check mode constraints */ + switch (size) { + case I2C_SMBUS_BYTE: + case I2C_SMBUS_BYTE_DATA: + break; + case I2C_SMBUS_WORD_DATA: + if (!even || (!(first%2) && last%2)) + break; + /* Fall through */ + default: + fprintf(stderr, + "Error: Range parameter not compatible with selected mode!\n"); + exit(1); + } + } + + file = open_i2c_dev(i2cbus, filename, 0); + if (file < 0 + || check_funcs(file, size, pec) + || set_slave_addr(file, address, force)) + exit(1); + + if (pec) { + if (ioctl(file, I2C_PEC, 1) < 0) { + fprintf(stderr, "Error: Could not set PEC: %s\n", + strerror(errno)); + exit(1); + } + } + + if (!yes) { + fprintf(stderr, "WARNING! This program can confuse your I2C " + "bus, cause data loss and worse!\n"); + + fprintf(stderr, "I will probe file %s, address 0x%x, mode " + "%s\n", filename, address, + size == I2C_SMBUS_BLOCK_DATA ? "smbus block" : + size == I2C_SMBUS_I2C_BLOCK_DATA ? "i2c block" : + size == I2C_SMBUS_BYTE ? "byte consecutive read" : + size == I2C_SMBUS_BYTE_DATA ? "byte" : "word"); + if (pec) + fprintf(stderr, "PEC checking enabled.\n"); + if (even) + fprintf(stderr, "Only probing even register " + "addresses.\n"); + if (bank) { + if (size == I2C_SMBUS_BLOCK_DATA) + fprintf(stderr, "Using command 0x%02x.\n", + bank); + else + fprintf(stderr, "Probing bank %d using bank " + "register 0x%02x.\n", bank, bankreg); + } + if (range) { + fprintf(stderr, + "Probe range limited to 0x%02x-0x%02x.\n", + first, last); + } + + fprintf(stderr, "Continue? [Y/n] "); + fflush(stderr); + if (!user_ack(1)) { + fprintf(stderr, "Aborting on user request.\n"); + exit(0); + } + } + + /* See Winbond w83781d data sheet for bank details */ + if (bank && size != I2C_SMBUS_BLOCK_DATA) { + res = i2c_smbus_read_byte_data(file, bankreg); + if (res >= 0) { + old_bank = res; + res = i2c_smbus_write_byte_data(file, bankreg, + bank | (old_bank & 0xf0)); + } + if (res < 0) { + fprintf(stderr, "Error: Bank switching failed\n"); + exit(1); + } + } + + /* handle all but word data */ + if (size != I2C_SMBUS_WORD_DATA || even) { + /* do the block transaction */ + if (size == I2C_SMBUS_BLOCK_DATA + || size == I2C_SMBUS_I2C_BLOCK_DATA) { + unsigned char cblock[288]; + + if (size == I2C_SMBUS_BLOCK_DATA) { + res = i2c_smbus_read_block_data(file, bank, + cblock); + /* Remember returned block length for a nicer + display later */ + s_length = res; + } else { + for (res = 0; res < 256; res += i) { + i = i2c_smbus_read_i2c_block_data(file, + res, 32, cblock + res); + if (i <= 0) { + res = i; + break; + } + } + } + if (res <= 0) { + fprintf(stderr, "Error: Block read failed, " + "return code %d\n", res); + exit(1); + } + if (res >= 256) + res = 256; + for (i = 0; i < res; i++) + block[i] = cblock[i]; + if (size != I2C_SMBUS_BLOCK_DATA) + for (i = res; i < 256; i++) + block[i] = -1; + } + + if (size == I2C_SMBUS_BYTE) { + res = i2c_smbus_write_byte(file, first); + if(res != 0) { + fprintf(stderr, "Error: Write start address " + "failed, return code %d\n", res); + exit(1); + } + } + + printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f" + " 0123456789abcdef\n"); + for (i = 0; i < 256; i+=16) { + if (size == I2C_SMBUS_BLOCK_DATA && i >= s_length) + break; + if (i/16 < first/16) + continue; + if (i/16 > last/16) + break; + + printf("%02x: ", i); + for (j = 0; j < 16; j++) { + fflush(stdout); + /* Skip unwanted registers */ + if (i+j < first || i+j > last) { + printf(" "); + if (size == I2C_SMBUS_WORD_DATA) { + printf(" "); + j++; + } + continue; + } + + if (size == I2C_SMBUS_BYTE_DATA) { + block[i+j] = res = + i2c_smbus_read_byte_data(file, i+j); + } else if (size == I2C_SMBUS_WORD_DATA) { + res = i2c_smbus_read_word_data(file, + 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; + } + } else if (size == I2C_SMBUS_BYTE) { + block[i+j] = res = + i2c_smbus_read_byte(file); + } else + res = block[i+j]; + + if (size == I2C_SMBUS_BLOCK_DATA + && i+j >= s_length) { + printf(" "); + } else if (res < 0) { + printf("XX "); + if (size == I2C_SMBUS_WORD_DATA) + printf("XX "); + } else { + printf("%02x ", block[i+j]); + if (size == I2C_SMBUS_WORD_DATA) + printf("%02x ", block[i+j+1]); + } + if (size == I2C_SMBUS_WORD_DATA) + j++; + } + printf(" "); + + for (j = 0; j < 16; j++) { + if (size == I2C_SMBUS_BLOCK_DATA + && i+j >= s_length) + break; + /* Skip unwanted registers */ + if (i+j < first || i+j > last) { + printf(" "); + continue; + } + + res = block[i+j]; + if (res < 0) + printf("X"); + else + if ((res & 0xff) == 0x00 + || (res & 0xff) == 0xff) + printf("."); + else + if ((res & 0xff) < 32 + || (res & 0xff) >= 127) + printf("?"); + else + printf("%c", res & 0xff); + } + printf("\n"); + } + } else { + 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(file, i+j); + if (res < 0) + printf("XXXX "); + else + printf("%04x ", res & 0xffff); + } + printf("\n"); + } + } + if (bank && size != I2C_SMBUS_BLOCK_DATA) { + i2c_smbus_write_byte_data(file, bankreg, old_bank); + } + exit(0); +} diff --git a/tools/i2cget.8 b/tools/i2cget.8 new file mode 100644 index 0000000..8786603 --- /dev/null +++ b/tools/i2cget.8 @@ -0,0 +1,68 @@ +.TH I2CGET 8 "May 2008" +.SH "NAME" +i2cget \- read from I2C/SMBus chip registers + +.SH SYNOPSIS +.B i2cget +.RB [ -f ] +.RB [ -y ] +.I i2cbus +.I chip-address +.RI [ "data-address " [ mode ]] +.br +.B i2cget +.B -V + +.SH DESCRIPTION +i2cget is a small helper program to read registers visible through the I2C +bus (or SMBus). + +.SH OPTIONS +.TP +.B -V +Display the version and exit. +.TP +.B -f +Force access to the device even if it is already busy. By default, i2cget +will refuse to access a device which is already under the control of a +kernel driver. Using this flag is dangerous, it can seriously confuse the +kernel driver in question. It can also cause i2cget to return an invalid +value. So use at your own risk and only if you know what you're doing. +.TP +.B -y +Disable interactive mode. By default, i2cget will wait for a confirmation +from the user before messing with the I2C bus. When this flag is used, it +will perform the operation directly. This is mainly meant to be used in +scripts. Use with caution. +.PP +There are two required options to i2cget. \fIi2cbus\fR indicates the number +or name of the I2C bus to be scanned. This number should correspond to one of +the busses listed by \fIi2cdetect -l\fR. \fIchip-address\fR specifies the +address of the chip on that bus, and is an integer between 0x03 and 0x77. +.PP +\fIdata-address\fR specifies the address on that chip to read from, and is +an integer between 0x00 and 0xFF. If omitted, the currently active register +will be read (if that makes sense for the considered chip). +.PP +The \fImode\fR parameter, if specified, is one of the letters \fBb\fP, +\fBw\fP or \fBc\fP, corresponding to a read byte data, a read word data or a +write byte/read byte transaction, respectively. A \fBp\fP can also be appended +to the \fImode\fR parameter to enable PEC. If the \fImode\fR parameter is omitted, +i2cget defaults to a read byte data transaction, unless \fIdata-address\fR is +also omitted, in which case the default (and only valid) transaction is a +single read byte. + +.SH WARNING +i2cget can be extremely dangerous if used improperly. I2C and SMBus are designed +in such a way that an SMBus read transaction can be seen as a write transaction by +certain chips. This is particularly true if setting \fImode\fR to \fBcp\fP (write byte/read +byte with PEC). Be extremely careful using this program. + +.SH SEE ALSO +i2cdump(8), i2cset(8) + +.SH AUTHOR +Jean Delvare + +This manual page was strongly inspired from those written by David Z Maze +for i2cset. diff --git a/tools/i2cget.c b/tools/i2cget.c new file mode 100644 index 0000000..32a68e5 --- /dev/null +++ b/tools/i2cget.c @@ -0,0 +1,256 @@ +/* + i2cget.c - A user-space program to read an I2C register. + Copyright (C) 2005-2008 Jean Delvare <khali@linux-fr.org> + + Based on i2cset.c: + Copyright (C) 2001-2003 Frodo Looijaard <frodol@dds.nl>, and + Mark D. Studebaker <mdsxyz123@yahoo.com> + Copyright (C) 2004-2005 Jean Delvare <khali@linux-fr.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02110-1301 USA. +*/ + +#include <errno.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <linux/i2c-dev.h> +#include "i2cbusses.h" +#include "util.h" +#include "../version.h" + +static void help(void) __attribute__ ((noreturn)); + +static void help(void) +{ + fprintf(stderr, + "Usage: i2cget [-f] [-y] I2CBUS CHIP-ADDRESS [DATA-ADDRESS [MODE]]\n" + " I2CBUS is an integer or an I2C bus name\n" + " ADDRESS is an integer (0x03 - 0x77)\n" + " MODE is one of:\n" + " b (read byte data, default)\n" + " w (read word data)\n" + " c (write byte/read byte)\n" + " Append p for SMBus PEC\n"); + exit(1); +} + +static int check_funcs(int file, int size, int daddress, int pec) +{ + unsigned long funcs; + + /* check adapter functionality */ + if (ioctl(file, I2C_FUNCS, &funcs) < 0) { + fprintf(stderr, "Error: Could not get the adapter " + "functionality matrix: %s\n", strerror(errno)); + return -1; + } + + switch (size) { + case I2C_SMBUS_BYTE: + if (!(funcs & I2C_FUNC_SMBUS_READ_BYTE)) { + fprintf(stderr, MISSING_FUNC_FMT, "SMBus receive byte"); + return -1; + } + if (daddress >= 0 + && !(funcs & I2C_FUNC_SMBUS_WRITE_BYTE)) { + fprintf(stderr, MISSING_FUNC_FMT, "SMBus send byte"); + return -1; + } + break; + + case I2C_SMBUS_BYTE_DATA: + if (!(funcs & I2C_FUNC_SMBUS_READ_BYTE_DATA)) { + fprintf(stderr, MISSING_FUNC_FMT, "SMBus read byte"); + return -1; + } + break; + + case I2C_SMBUS_WORD_DATA: + if (!(funcs & I2C_FUNC_SMBUS_READ_WORD_DATA)) { + fprintf(stderr, MISSING_FUNC_FMT, "SMBus read word"); + return -1; + } + break; + } + + if (pec + && !(funcs & (I2C_FUNC_SMBUS_PEC | I2C_FUNC_I2C))) { + fprintf(stderr, "Warning: Adapter does " + "not seem to support PEC\n"); + } + + return 0; +} + +static int confirm(const char *filename, int address, int size, int daddress, + int pec) +{ + int dont = 0; + + fprintf(stderr, "WARNING! This program can confuse your I2C " + "bus, cause data loss and worse!\n"); + + /* Don't let the user break his/her EEPROMs */ + if (address >= 0x50 && address <= 0x57 && pec) { + fprintf(stderr, "STOP! EEPROMs are I2C devices, not " + "SMBus devices. Using PEC\non I2C devices may " + "result in unexpected results, such as\n" + "trashing the contents of EEPROMs. We can't " + "let you do that, sorry.\n"); + return 0; + } + + if (size == I2C_SMBUS_BYTE && daddress >= 0 && pec) { + fprintf(stderr, "WARNING! All I2C chips and some SMBus chips " + "will interpret a write\nbyte command with PEC as a" + "write byte data command, effectively writing a\n" + "value into a register!\n"); + dont++; + } + + fprintf(stderr, "I will read from device file %s, chip " + "address 0x%02x, ", filename, address); + if (daddress < 0) + fprintf(stderr, "current data\naddress"); + else + fprintf(stderr, "data address\n0x%02x", daddress); + fprintf(stderr, ", using %s.\n", + size == I2C_SMBUS_BYTE ? (daddress < 0 ? + "read byte" : "write byte/read byte") : + size == I2C_SMBUS_BYTE_DATA ? "read byte data" : + "read word data"); + if (pec) + fprintf(stderr, "PEC checking enabled.\n"); + + fprintf(stderr, "Continue? [%s] ", dont ? "y/N" : "Y/n"); + fflush(stderr); + if (!user_ack(!dont)) { + fprintf(stderr, "Aborting on user request.\n"); + return 0; + } + + return 1; +} + +int main(int argc, char *argv[]) +{ + char *end; + int res, i2cbus, address, size, file; + int daddress; + char filename[20]; + int pec = 0; + int flags = 0; + int force = 0, yes = 0, version = 0; + + /* handle (optional) flags first */ + while (1+flags < argc && argv[1+flags][0] == '-') { + switch (argv[1+flags][1]) { + case 'V': version = 1; break; + case 'f': force = 1; break; + case 'y': yes = 1; break; + default: + fprintf(stderr, "Error: Unsupported option " + "\"%s\"!\n", argv[1+flags]); + help(); + exit(1); + } + flags++; + } + + if (version) { + fprintf(stderr, "i2cget version %s\n", VERSION); + exit(0); + } + + if (argc < flags + 3) + help(); + + i2cbus = lookup_i2c_bus(argv[flags+1]); + if (i2cbus < 0) + help(); + + address = parse_i2c_address(argv[flags+2]); + if (address < 0) + help(); + + if (argc > flags + 3) { + size = I2C_SMBUS_BYTE_DATA; + daddress = strtol(argv[flags+3], &end, 0); + if (*end || daddress < 0 || daddress > 0xff) { + fprintf(stderr, "Error: Data address invalid!\n"); + help(); + } + } else { + size = I2C_SMBUS_BYTE; + daddress = -1; + } + + if (argc > flags + 4) { + switch (argv[flags+4][0]) { + case 'b': size = I2C_SMBUS_BYTE_DATA; break; + case 'w': size = I2C_SMBUS_WORD_DATA; break; + case 'c': size = I2C_SMBUS_BYTE; break; + default: + fprintf(stderr, "Error: Invalid mode!\n"); + help(); + } + pec = argv[flags+4][1] == 'p'; + } + + file = open_i2c_dev(i2cbus, filename, 0); + if (file < 0 + || check_funcs(file, size, daddress, pec) + || set_slave_addr(file, address, force)) + exit(1); + + if (!yes && !confirm(filename, address, size, daddress, pec)) + exit(0); + + if (pec && ioctl(file, I2C_PEC, 1) < 0) { + fprintf(stderr, "Error: Could not set PEC: %s\n", + strerror(errno)); + close(file); + exit(1); + } + + switch (size) { + case I2C_SMBUS_BYTE: + if (daddress >= 0) { + res = i2c_smbus_write_byte(file, daddress); + if (res < 0) + fprintf(stderr, "Warning - write failed\n"); + } + res = i2c_smbus_read_byte(file); + break; + case I2C_SMBUS_WORD_DATA: + res = i2c_smbus_read_word_data(file, daddress); + break; + default: /* I2C_SMBUS_BYTE_DATA */ + res = i2c_smbus_read_byte_data(file, daddress); + } + close(file); + + if (res < 0) { + fprintf(stderr, "Error: Read failed\n"); + exit(2); + } + + printf("0x%0*x\n", size == I2C_SMBUS_WORD_DATA ? 4 : 2, res); + + exit(0); +} diff --git a/tools/i2cset.8 b/tools/i2cset.8 new file mode 100644 index 0000000..2b4ce3a --- /dev/null +++ b/tools/i2cset.8 @@ -0,0 +1,91 @@ +.TH I2CSET 8 "November 2008" +.SH "NAME" +i2cset \- set I2C registers + +.SH SYNOPSIS +.B i2cset +.RB [ -f ] +.RB [ -y ] +.RB [ "-m mask" ] +.RB [ -r ] +.I i2cbus +.I chip-address +.I data-address +.RI [ "value " [ "mode" ]] +.br +.B i2cset +.B -V + +.SH DESCRIPTION +i2cset is a small helper program to set registers visible through the I2C +bus. + +.SH OPTIONS +.TP +.B -V +Display the version and exit. +.TP +.B -f +Force access to the device even if it is already busy. By default, i2cset +will refuse to access a device which is already under the control of a +kernel driver. Using this flag is dangerous, it can seriously confuse the +kernel driver in question. It can also cause i2cset to silently write to +the wrong register. So use at your own risk and only if you know what +you're doing. +.TP +.B -y +Disable interactive mode. By default, i2cset will wait for a confirmation +from the user before messing with the I2C bus. When this flag is used, it +will perform the operation directly. This is mainly meant to be used in +scripts. +.TP +.B -m mask +The \fImask\fR parameter, if specified, describes which bits of \fIvalue\fR +will be actually written to \fIdata-address\fR. Bits set to 1 in the mask +are taken from \fIvalue\fR, while bits set to 0 will be read from +\fIdata-address\fR and thus preserved by the operation. Please note that +this parameter assumes that the read and write operations for the specified +mode are symmetrical for the device you are accessing. This may or may not +be the case, as neither I2C nor SMBus guarantees this. +.TP +.B -r +Read back the value right after writing it, and compare the result with the +value written. This used to be the default behavior. The same limitations +apply as those of option \fB-m\fR. +.PP +There are three required options to i2cset. \fIi2cbus\fR indicates the number +or name of the I2C bus to be scanned. This number should correspond to one of +the busses listed by \fIi2cdetect -l\fR. \fIchip-address\fR specifies the +address of the chip on that bus, and is an integer between 0x03 and 0x77. +\fIdata-address\fR specifies the address on that chip to write to, and is an +integer between 0x00 and 0xFF. +.PP +The \fIvalue\fR parameter, if specified, is the value to write to that +location on the chip. If this parameter is omited, then a short write is +issued. For most chips, it simply sets an internal pointer to the target +location, but doesn't actually write to that location. For a few chips +though, in particular simple ones with a single register, this short write +is an actual write. +.PP +The \fImode\fR parameter, if specified, is one of the letters \fBb\fP or +\fBw\fP, corresponding to a write size of a single byte or a 16-bit word, +respectively. A \fBp\fP can also be appended to the \fImode\fR parameter to +enable PEC. If the \fImode\fR parameter is omitted, i2cset defaults to byte +mode without PEC. The \fIvalue\fR provided must be within range for the +specified data type (0x00-0xFF for bytes, 0x0000-0xFFFF for words). + +.SH WARNING +i2cset can be extremely dangerous if used improperly. It can confuse your +I2C bus, cause data loss, or have more serious side effects. Writing to +a serial EEPROM on a memory DIMM (chip addresses between 0x50 and 0x57) may +DESTROY your memory, leaving your system unbootable! Be extremely careful +using this program. + +.SH SEE ALSO +i2cdump(8), isaset(8) + +.SH AUTHOR +Frodo Looijaard, Mark D. Studebaker and Jean Delvare + +This manual page was originally written by David Z Maze <dmaze@debian.org> for +the Debian GNU/Linux system. diff --git a/tools/i2cset.c b/tools/i2cset.c new file mode 100644 index 0000000..296abe1 --- /dev/null +++ b/tools/i2cset.c @@ -0,0 +1,344 @@ +/* + i2cset.c - A user-space program to write an I2C register. + Copyright (C) 2001-2003 Frodo Looijaard <frodol@dds.nl>, and + Mark D. Studebaker <mdsxyz123@yahoo.com> + Copyright (C) 2004-2008 Jean Delvare <khali@linux-fr.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02110-1301 USA. +*/ + +#include <errno.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <linux/i2c-dev.h> +#include "i2cbusses.h" +#include "util.h" +#include "../version.h" + +static void help(void) __attribute__ ((noreturn)); + +static void help(void) +{ + fprintf(stderr, + "Usage: i2cset [-f] [-y] [-m MASK] I2CBUS CHIP-ADDRESS DATA-ADDRESS [VALUE [MODE]]\n" + " I2CBUS is an integer or an I2C bus name\n" + " ADDRESS is an integer (0x03 - 0x77)\n" + " MODE is one of:\n" + " b (byte, default)\n" + " w (word)\n" + " Append p for SMBus PEC\n"); + exit(1); +} + +static int check_funcs(int file, int size, int pec) +{ + unsigned long funcs; + + /* check adapter functionality */ + if (ioctl(file, I2C_FUNCS, &funcs) < 0) { + fprintf(stderr, "Error: Could not get the adapter " + "functionality matrix: %s\n", strerror(errno)); + return -1; + } + + switch (size) { + case I2C_SMBUS_BYTE: + if (!(funcs & I2C_FUNC_SMBUS_WRITE_BYTE)) { + fprintf(stderr, MISSING_FUNC_FMT, "SMBus send byte"); + return -1; + } + break; + + case I2C_SMBUS_BYTE_DATA: + if (!(funcs & I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) { + fprintf(stderr, MISSING_FUNC_FMT, "SMBus write byte"); + return -1; + } + break; + + case I2C_SMBUS_WORD_DATA: + if (!(funcs & I2C_FUNC_SMBUS_WRITE_WORD_DATA)) { + fprintf(stderr, MISSING_FUNC_FMT, "SMBus write word"); + return -1; + } + break; + } + + if (pec + && !(funcs & (I2C_FUNC_SMBUS_PEC | I2C_FUNC_I2C))) { + fprintf(stderr, "Warning: Adapter does " + "not seem to support PEC\n"); + } + + return 0; +} + +static int confirm(const char *filename, int address, int size, int daddress, + int value, int vmask, int pec) +{ + int dont = 0; + + fprintf(stderr, "WARNING! This program can confuse your I2C " + "bus, cause data loss and worse!\n"); + + if (address >= 0x50 && address <= 0x57) { + fprintf(stderr, "DANGEROUS! Writing to a serial " + "EEPROM on a memory DIMM\nmay render your " + "memory USELESS and make your system " + "UNBOOTABLE!\n"); + dont++; + } + + fprintf(stderr, "I will write to device file %s, chip address " + "0x%02x, data address\n0x%02x, ", filename, address, daddress); + if (size == I2C_SMBUS_BYTE) + fprintf(stderr, "no data.\n"); + else + fprintf(stderr, "data 0x%02x%s, mode %s.\n", value, + vmask ? " (masked)" : "", + size == I2C_SMBUS_BYTE_DATA ? "byte" : "word"); + if (pec) + fprintf(stderr, "PEC checking enabled.\n"); + + fprintf(stderr, "Continue? [%s] ", dont ? "y/N" : "Y/n"); + fflush(stderr); + if (!user_ack(!dont)) { + fprintf(stderr, "Aborting on user request.\n"); + return 0; + } + + return 1; +} + +int main(int argc, char *argv[]) +{ + char *end; + const char *maskp = NULL; + int res, i2cbus, address, size, file; + int value, daddress, vmask = 0; + char filename[20]; + int pec = 0; + int flags = 0; + int force = 0, yes = 0, version = 0, readback = 0; + + /* handle (optional) flags first */ + while (1+flags < argc && argv[1+flags][0] == '-') { + switch (argv[1+flags][1]) { + case 'V': version = 1; break; + case 'f': force = 1; break; + case 'y': yes = 1; break; + case 'm': + if (2+flags < argc) + maskp = argv[2+flags]; + flags++; + break; + case 'r': readback = 1; break; + default: + fprintf(stderr, "Error: Unsupported option " + "\"%s\"!\n", argv[1+flags]); + help(); + exit(1); + } + flags++; + } + + if (version) { + fprintf(stderr, "i2cset version %s\n", VERSION); + exit(0); + } + + if (argc < flags + 4) + help(); + + i2cbus = lookup_i2c_bus(argv[flags+1]); + if (i2cbus < 0) + help(); + + address = parse_i2c_address(argv[flags+2]); + if (address < 0) + help(); + + daddress = strtol(argv[flags+3], &end, 0); + if (*end || daddress < 0 || daddress > 0xff) { + fprintf(stderr, "Error: Data address invalid!\n"); + help(); + } + + if (argc > flags + 4) { + size = I2C_SMBUS_BYTE_DATA; + value = strtol(argv[flags+4], &end, 0); + if (*end || value < 0) { + fprintf(stderr, "Error: Data value invalid!\n"); + help(); + } + } else { + size = I2C_SMBUS_BYTE; + value = -1; + } + + if (argc > flags + 5) { + switch (argv[flags+5][0]) { + case 'b': size = I2C_SMBUS_BYTE_DATA; break; + case 'w': size = I2C_SMBUS_WORD_DATA; break; + default: + fprintf(stderr, "Error: Invalid mode!\n"); + help(); + } + pec = argv[flags+5][1] == 'p'; + } + + /* Old method to provide the value mask, deprecated and no longer + documented but still supported for compatibility */ + if (argc > flags + 6) { + if (maskp) { + fprintf(stderr, "Error: Data value mask provided twice!\n"); + help(); + } + fprintf(stderr, "Warning: Using deprecated way to set the data value mask!\n"); + fprintf(stderr, " Please switch to using -m.\n"); + maskp = argv[flags+6]; + } + + if (maskp) { + vmask = strtol(maskp, &end, 0); + if (*end || vmask == 0) { + fprintf(stderr, "Error: Data value mask invalid!\n"); + help(); + } + } + + if ((size == I2C_SMBUS_BYTE_DATA && value > 0xff) + || (size == I2C_SMBUS_WORD_DATA && value > 0xffff)) { + fprintf(stderr, "Error: Data value out of range!\n"); + help(); + } + + file = open_i2c_dev(i2cbus, filename, 0); + if (file < 0 + || check_funcs(file, size, pec) + || set_slave_addr(file, address, force)) + exit(1); + + if (!yes && !confirm(filename, address, size, daddress, + value, vmask, pec)) + exit(0); + + if (vmask) { + int oldvalue; + + switch (size) { + case I2C_SMBUS_BYTE: + oldvalue = i2c_smbus_read_byte(file); + break; + case I2C_SMBUS_WORD_DATA: + oldvalue = i2c_smbus_read_word_data(file, daddress); + break; + default: + oldvalue = i2c_smbus_read_byte_data(file, daddress); + } + + if (oldvalue < 0) { + fprintf(stderr, "Error: Failed to read old value\n"); + exit(1); + } + + value = (value & vmask) | (oldvalue & ~vmask); + + if (!yes) { + fprintf(stderr, "Old value 0x%0*x, write mask " + "0x%0*x: Will write 0x%0*x to register " + "0x%02x\n", + size == I2C_SMBUS_WORD_DATA ? 4 : 2, oldvalue, + size == I2C_SMBUS_WORD_DATA ? 4 : 2, vmask, + size == I2C_SMBUS_WORD_DATA ? 4 : 2, value, + daddress); + + fprintf(stderr, "Continue? [Y/n] "); + fflush(stderr); + if (!user_ack(1)) { + fprintf(stderr, "Aborting on user request.\n"); + exit(0); + } + } + } + + if (pec && ioctl(file, I2C_PEC, 1) < 0) { + fprintf(stderr, "Error: Could not set PEC: %s\n", + strerror(errno)); + close(file); + exit(1); + } + + switch (size) { + case I2C_SMBUS_BYTE: + res = i2c_smbus_write_byte(file, daddress); + break; + case I2C_SMBUS_WORD_DATA: + res = i2c_smbus_write_word_data(file, daddress, value); + break; + default: /* I2C_SMBUS_BYTE_DATA */ + res = i2c_smbus_write_byte_data(file, daddress, value); + } + if (res < 0) { + fprintf(stderr, "Error: Write failed\n"); + close(file); + exit(1); + } + + if (pec) { + if (ioctl(file, I2C_PEC, 0) < 0) { + fprintf(stderr, "Error: Could not clear PEC: %s\n", + strerror(errno)); + close(file); + exit(1); + } + } + + if (!readback) { /* We're done */ + close(file); + exit(0); + } + + switch (size) { + case I2C_SMBUS_BYTE: + res = i2c_smbus_read_byte(file); + value = daddress; + break; + case I2C_SMBUS_WORD_DATA: + res = i2c_smbus_read_word_data(file, daddress); + break; + default: /* I2C_SMBUS_BYTE_DATA */ + res = i2c_smbus_read_byte_data(file, daddress); + } + close(file); + + if (res < 0) { + printf("Warning - readback failed\n"); + } else + if (res != value) { + printf("Warning - data mismatch - wrote " + "0x%0*x, read back 0x%0*x\n", + size == I2C_SMBUS_WORD_DATA ? 4 : 2, value, + size == I2C_SMBUS_WORD_DATA ? 4 : 2, res); + } else { + printf("Value 0x%0*x written, readback matched\n", + size == I2C_SMBUS_WORD_DATA ? 4 : 2, value); + } + + exit(0); +} diff --git a/tools/util.c b/tools/util.c new file mode 100644 index 0000000..029719e --- /dev/null +++ b/tools/util.c @@ -0,0 +1,48 @@ +/* + util.c - helper functions + Copyright (C) 2006 Jean Delvare <khali@linux-fr.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. +*/ + +#include <stdio.h> +#include "util.h" + +/* Return 1 if we should continue, 0 if we should abort */ +int user_ack(int def) +{ + char s[2]; + int ret; + + if (!fgets(s, 2, stdin)) + return 0; /* Nack by default */ + + switch (s[0]) { + case 'y': + case 'Y': + ret = 1; + break; + case 'n': + case 'N': + ret = 0; + break; + default: + ret = def; + } + + /* Flush extra characters */ + while (s[0] != '\n') { + int c = fgetc(stdin); + if (c == EOF) { + ret = 0; + break; + } + s[0] = c; + } + + return ret; +} + diff --git a/tools/util.h b/tools/util.h new file mode 100644 index 0000000..a179b19 --- /dev/null +++ b/tools/util.h @@ -0,0 +1,16 @@ +/* + util - helper functions + Copyright (C) 2006 Jean Delvare <khali@linux-fr.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. +*/ + +#ifndef _UTIL_H +#define _UTIL_H + +extern int user_ack(int def); + +#endif /* _UTIL_H */ |