/* i2cdetect.c - a user-space program to scan for I2C devices Copyright (C) 1999-2004 Frodo Looijaard , and Mark D. Studebaker Copyright (C) 2004-2010 Jean Delvare 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 #include #include #include #include #include #include "i2cbusses.h" #include "../version.h" #define BBA_PLATFORM #ifdef BBA_PLATFORM #include "../../../../netapp/common/platform/libsrc/hwdd_lib_platform_id.h" #include #endif #define MODE_AUTO 0 #define MODE_QUICK 1 #define MODE_READ 2 #define MODE_FUNC 3 #ifdef BBA_PLATFORM #define BIMOTA_PCI_DIMM_BUS_CPU0 127 #define BIMOTA_PCI_DIMM_BUS_CPU1 0xff #define BUELL_PCI_DIMM_BUS_CPU0 0xff #define MAX_CONFIG_SPACE 0x10 #define PCI_DIMM_DOMAIN 0 #define PCI_DIMM_DEVICE 15 #define PCI_DIMM_FUNC 0 #define PCI_DIMM_SMB_OFFSET_0 0x180 #define PCI_DIMM_SMB_OFFSET_1 (PCI_DIMM_SMB_OFFSET_0 + MAX_CONFIG_SPACE) //Error types #define SPD_GEN_FAIL -6 #define SPD_BUSY_ERR -5 #define SMBUS_CNTRL_FAIL -4 #define SPD_PCI_FAIL -3 #define SPD_TIME_OUT_ERR -2 #define SPD_ERR -1 #define SPD_SUCCESS 0 #define SPD_I2C_WAIT 1000 #define I2C_LOOP_MAX_TIME 35 /* Register set */ #define SNB_SMB_STAT 0 #define SNB_SMB_CMD 4 #define SNB_SMB_CNTL 8 /* Bit fields */ #define BIT(s) (1ULL << s) /* Status register */ #define SNB_SMB_STAT_RDO BIT(31) #define SNB_SMB_STAT_WOD BIT(30) #define SNB_SMB_STAT_SBE BIT(29) #define SNB_SMB_STAT_BUSY BIT(28) /* Command register */ #define SNB_SMB_CMD_TRIGGER BIT(31) #define SNB_SMB_CMD_PNTR_SEL BIT(30) #define SNB_SMB_CMD_WORD_ACCESS BIT(29) #define SNB_SMB_CMD_WRT_PNTR BIT(28) #define SNB_SMB_CMD_WRT_CMD BIT(27) /* Control register */ #define SNB_SMB_CNTL_CKOVRD BIT(27) #define SNB_SMB_CNTL_DIS_WRT BIT(26) #define SNB_SMB_CNTL_SOFT_RST BIT(10) #define SNB_SMB_CNTL_TSOD_EN BIT(8) #define SNB_SMB_CMD_SA 24 #define SNB_SMB_CMD_BA 16 #define SNB_SMB_CNTL_DTI 28 #define SNB_SMB_CMD_SA_CLR ~(0x7 << SNB_SMB_CMD_SA) #define SNB_SMB_CMD_BA_CLR ~(0xff << SNB_SMB_CMD_BA) #define SNB_SMB_CNTL_DTI_CLR ~(0xf << SNB_SMB_CNTL_DTI) struct cpu_bus_info { struct pci_access *pacc; struct pci_dev *pci_d; unsigned char plat_id; unsigned char domain; unsigned char bus; unsigned char device; unsigned char func; unsigned long int offset; }; typedef struct cpu_bus_info cpu_bus_info_t; //Function prototypes int perform_pci_init(cpu_bus_info_t *info, int bus, int instance); int is_slave_preset_on_cpu(cpu_bus_info_t *info,unsigned char saddr); int wait_for_completion(cpu_bus_info_t *info); int send_cmd(cpu_bus_info_t *info, unsigned char offset, unsigned long int* cmd); int read_reg(cpu_bus_info_t *info, unsigned char offset, unsigned long int* cmd); int reset_smbus_master(cpu_bus_info_t *info); #endif static void help(void) { #ifdef BBA_PLATFORM fprintf(stderr, "Usage: i2cdetect [-y] [-a] [-q|-r] I2CBUS [FIRST LAST]\n" " i2cdetect -F I2CBUS\n" " i2cdetect -l\n" " i2cdetect -c [-a] I2CBUS [FIRST LAST] CPUNUM\n" " CPUNUM is an integer referring to which CPU.\n" " I2CBUS is an integer or an I2C bus name\n" " If provided, FIRST and LAST limit the probing range.\n"); #else 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"); #endif } #ifdef BBA_PLATFORM int perform_pci_init(cpu_bus_info_t *info, int bus_num,int cpu_num) { int ret = SPD_SUCCESS; unsigned long int reg = 0; if (bus_num > 1) { fprintf(stderr,"Platform doesn't I2CBUS > 1\n"); return SPD_GEN_FAIL; } switch (info->plat_id) { case ID_BIMOTA_M : { if (cpu_num == 1) { info->bus = BIMOTA_PCI_DIMM_BUS_CPU1; info->offset = PCI_DIMM_SMB_OFFSET_1; } else if (cpu_num == 0){ //For CPU 0 info->bus = BIMOTA_PCI_DIMM_BUS_CPU0; info->offset = PCI_DIMM_SMB_OFFSET_0; } else { fprintf(stderr, "Platform supports two cpu only!\n"); return SPD_GEN_FAIL; } } break; case ID_BIMOTA_L : case ID_BUELL : { if (cpu_num == 0) { info->bus = BUELL_PCI_DIMM_BUS_CPU0; info->offset = PCI_DIMM_SMB_OFFSET_0; } else { fprintf(stderr,"Platform supports single CPU only!\n"); return SPD_GEN_FAIL; } } break; } // info->bus = BUELL_PCI_DIMM_BUS_CPU0; info->pacc = pci_alloc(); if (info->pacc == NULL) { fprintf(stderr,"pci_alloc() failed, error:%s\n",strerror(errno)); return SPD_PCI_FAIL; } pci_init(info->pacc); pci_scan_bus(info->pacc); info->domain = PCI_DIMM_DOMAIN; info->device = PCI_DIMM_DEVICE; info->func = PCI_DIMM_FUNC; info->pci_d = pci_get_dev(info->pacc,info->domain,info->bus,info->device,info->func); if (info->pci_d == NULL) { fprintf(stderr,"pci_get_dev() failed, error:%s\n",strerror(errno)); return SPD_PCI_FAIL; } if ((ret = read_reg(info,SNB_SMB_CNTL,®)) == SPD_SUCCESS) { if (reg & SNB_SMB_CNTL_TSOD_EN) { fprintf(stderr,"Error: TSOD Polling is enabled\n"); ret = SPD_GEN_FAIL; } } else { fprintf(stderr,"Error: Failed while reading SNB_SMB_CNTL\n"); } return ret; } int send_cmd(cpu_bus_info_t *info, unsigned char offset, unsigned long int* cmd) { int8_t ret = SPD_SUCCESS; if ((pci_write_block(info->pci_d,(info->offset + offset), (u8 *)cmd, 4)) < 0 ) { fprintf(stderr,"pci write failed, error:%s\n",strerror(errno)); ret = SPD_PCI_FAIL; } return ret; } int read_reg(cpu_bus_info_t *info, unsigned char offset, unsigned long int* cmd) { int8_t ret = SPD_SUCCESS; if ((pci_read_block(info->pci_d,(info->offset + offset),(u8 *)cmd,4)) < 0) { fprintf(stderr,"pci read failed, error:%s\n",strerror(errno)); ret = SPD_PCI_FAIL; } return ret; } int reset_smbus_master(cpu_bus_info_t *info) { int8_t ret = SPD_SUCCESS; unsigned long int reg = 0; if ((ret = read_reg(info,SNB_SMB_CNTL,®)) == SPD_SUCCESS) { reg = SNB_SMB_CNTL_SOFT_RST | (reg & ~SNB_SMB_CNTL_CKOVRD); if ((ret = send_cmd(info,SNB_SMB_CNTL,®)) == SPD_SUCCESS) { //Sleep for 35ms usleep(SPD_I2C_WAIT * I2C_LOOP_MAX_TIME); reg = (reg & ~SNB_SMB_CNTL_SOFT_RST) | SNB_SMB_CNTL_CKOVRD; if ((ret = send_cmd(info,SNB_SMB_CNTL,®)) == SPD_SUCCESS) { fprintf(stderr,"Resetting of SMBus master successful.\n"); } else { fprintf(stderr,"Error occured while reseting SNB_SMB_CNTL_SOFT_RST\n"); } } else { fprintf(stderr,"Error occured while setting SNB_SMB_CNTL_SOFT_RST reg\n"); } } else { fprintf(stderr,"Error occured while reading SNB_SMB_CNTL reg\n"); } return ret; } int is_slave_preset_on_cpu(cpu_bus_info_t *info,unsigned char saddr) { /** * 7-bit i2c address constitutes DTI = [6:3] bits and SA = [2:0] */ int8_t ret = SPD_SUCCESS; unsigned long int reg = 0; if ((ret = read_reg(info,SNB_SMB_STAT,®)) == SPD_SUCCESS) { if (reg & SNB_SMB_STAT_BUSY) { fprintf(stderr,"Error: SMBus Master is Busy\n"); return SPD_BUSY_ERR; } } else { fprintf(stderr,"Error: While reading SNB_SMB_STAT reg\n"); return ret; } if ((ret = read_reg(info,SNB_SMB_CNTL,®)) == SPD_SUCCESS) { //Clear and Update DTI reg &= SNB_SMB_CNTL_DTI_CLR; reg |= (((saddr >> 3) & 0x0f) << SNB_SMB_CNTL_DTI); //DTI is part for CNTL register if ((ret = send_cmd(info,SNB_SMB_CNTL,®)) == SPD_SUCCESS) { reg = 0; if ((ret = read_reg(info,SNB_SMB_CMD,®)) == SPD_SUCCESS) { //Trigger I2c Communication by writing to CMD register reg |= SNB_SMB_CMD_TRIGGER; reg &= ~SNB_SMB_CMD_WRT_PNTR; reg &= ~SNB_SMB_CMD_WRT_CMD; reg &= ~SNB_SMB_CMD_WORD_ACCESS; reg &= SNB_SMB_CMD_SA_CLR; reg |= ((saddr & 0x7) << SNB_SMB_CMD_SA); reg |= SNB_SMB_CMD_PNTR_SEL; if ((ret = send_cmd(info,SNB_SMB_CMD,®)) == SPD_SUCCESS) { ret = wait_for_completion(info); if ((ret == SPD_TIME_OUT_ERR) || (ret == SMBUS_CNTRL_FAIL)) { //Incase of timeout, SMBus master might of hung //Lets reset it. reset_smbus_master(info); } } else { fprintf(stderr,"Error: while writing SNB_SMB_CMD reg\n"); } } else { fprintf(stderr,"Error: while reading SNB_SMB_CMD reg\n"); } } else { fprintf(stderr,"Error: while writing SNB_SMB_CNTL reg\n"); } } else { fprintf(stderr,"Error: while reading SNB_SMB_CNTL reg\n"); } return ret; } int wait_for_completion(cpu_bus_info_t *info) { int8_t ret = SPD_SUCCESS,loop = 0; unsigned long int reg = 0; do { loop++; //Sleep for 1ms usleep(SPD_I2C_WAIT); if ((ret = read_reg(info,SNB_SMB_CMD,®)) != 0) { fprintf(stderr,"Error: while reading SNB_SMB_CMD\n"); return ret; } } while ((reg & SNB_SMB_CMD_TRIGGER) && (loop <= I2C_LOOP_MAX_TIME)); if ((reg & SNB_SMB_CMD_TRIGGER)) { //After more than 35ms, if still no transaction means //and SMBus master reset fprintf(stderr,"Error: iMC SMBus Controller is not starting i2c transaction, resetting it\n"); return SMBUS_CNTRL_FAIL; } loop = 0; do { usleep(SPD_I2C_WAIT); if ((ret = read_reg(info,SNB_SMB_STAT,®)) != 0) { fprintf(stderr,"Error: while reading SNB_SMB_STAT\n"); return ret; } if (reg & SNB_SMB_STAT_SBE) { break; } loop ++; } while ((reg & SNB_SMB_STAT_BUSY) && (loop <= I2C_LOOP_MAX_TIME)); if (reg & SNB_SMB_STAT_BUSY) { //After more than 35ms, if still busy means slave is clock streching //and need master reset. fprintf(stderr,"Error: Timeout error occured\n"); return SPD_TIME_OUT_ERR; } if (reg & SNB_SMB_STAT_SBE) { //This means device address doesn't exist return SPD_ERR; } return ret; } #endif #ifdef BBA_PLATFORM static int scan_i2c_bus(int file, int mode, int first, int last, cpu_bus_info_t *info, int cpu_bus_scan) #else static int scan_i2c_bus(int file, int mode, int first, int last) #endif { 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; } #ifdef BBA_PLATFORM if (!cpu_bus_scan) { #endif /* 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); } #ifdef BBA_PLATFORM } else { res = is_slave_preset_on_cpu(info,(unsigned char)((i+j) & 0xff)); if (res != SPD_ERR && res != SPD_SUCCESS) { //Only in case of SPD_ERR, we will continue operation with next device return -1; } } #endif 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; #ifdef BBA_PLATFORM int cpu_bus_scan = 0, ext = 0,cpu_num = 0; cpu_bus_info_t info; #endif /* 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; #ifdef BBA_PLATFORM case 'c': cpu_bus_scan = 1; info.plat_id = get_platform_id(); if ((info.plat_id != ID_BUELL) && (info.plat_id != ID_BIMOTA_L) && (info.plat_id != ID_BIMOTA_M)) { fprintf(stderr,"CPU I2C Scan not supported on this platform\n"); exit(1); } break; #endif 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); } #ifdef BBA_PLATFORM if (cpu_bus_scan) { ext = 5; } else { ext = 4; } #endif /* read address range if present */ #ifdef BBA_PLATFORM if (argc == flags + ext && mode != MODE_FUNC) { #else if (argc == flags && mode != MODE_FUNC) { #endif 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; #ifdef BBA_PLATFORM if (cpu_bus_scan) { cpu_num = strtol(argv[flags+4], &end, 0); if (*end) { fprintf(stderr, "Error: CPU_NUM argument not a number!\n"); help(); exit(1); } } } else if (cpu_bus_scan) { if (argc != flags + 3) { help(); exit(1); } else { cpu_num = strtol(argv[flags+2], &end, 0); if (*end) { fprintf(stderr, "Error: CPU_NUM argument not a number!\n"); help(); exit(1); } } #endif } else if (argc != flags + 2) { help(); exit(1); } #ifdef BBA_PLATFORM if (!cpu_bus_scan) { #endif file = open_i2c_dev(i2cbus, filename, sizeof(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\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\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); } } #ifdef BBA_PLATFORM } else { if ((res = perform_pci_init(&info,i2cbus,cpu_num)) != SPD_SUCCESS) { if (info.pacc != NULL) { pci_cleanup(info.pacc); } return -1; } } res = scan_i2c_bus(file, mode, first, last,&info,cpu_bus_scan); #else res = scan_i2c_bus(file, mode, first, last); #endif #ifdef BBA_PLATFORM if (!cpu_bus_scan) { close(file); } else { pci_cleanup(info.pacc); } #else close(file); #endif exit(res?1:0); }