There's this www.pmbus.org thing that's slowly been getting under way, intended for digital power supply management. I think some recent Dell boxes use it internally. Wikipedia summarizes the tech fairly well; note the deltas from SMBus: http://en.wikipedia.org/wiki/PMBus When trying to talk to one such device, I put together the query utility appended to this message. I hope it will be useful to other folk who may need to access such devices from Linux. If nothing else it provides a consistent host-side interpretation of those specs, and calls out some ambiguous areas ... and it should be possible to build other tools based on this core code. - Dave /* $(CROSS)gcc -Wall -Os -o pmbus_peek pmbus_peek.c */ /* * pmbus_peek.c - initial userspace interrogation of PMBus devices * * Copyright (C) 2008 David Brownell * * 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 3 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, see <http://www.gnu.org/licenses/>. */ #include <errno.h> #include <fcntl.h> #include <math.h> #include <stdio.h> //#include <stdbool.h> #define bool u8 #define true 1 #define false 0 #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/ioctl.h> #include <linux/i2c.h> #include <linux/i2c-dev.h> #define HACK /* can issue no-arguments (W0) mfr-specific calls */ /* * This is a simple "tell me about that PMBus device" tool. It can probe * PMBus devices and (for PMBus 1.1 devices) show their self-advertised * capabilities. It knows about all the operations defined in the spec, * and can invoke a number of them. * * Note that those "self-advertised" capabilities are optional, and may * be better suited to firmware-based devices rather than pure hardware. * A microcontroller with just 2 KB of program memory can support these * capabilities, but a bit more memory would help it do Real Work too. * * Tested on Linux 2.6.24+ using the i2c-gpio bitbanger. * * See www.pmbus.org to get PMBus 1.1 (or newer) specifications. */ typedef __u8 u8; typedef __u16 u16; typedef __s8 s8; typedef __s16 s16; enum pmbus_cmd_type { /* _undef_ = 0, */ RW1 = 1, /* read/write one byte */ RW2, /* read/write two byte "word" */ RWB, /* read/write block (up to 255 bytes) */ RWB14, /* read/write block (of 14 bytes) */ RWP_QUERY, /* block write/read process call for QUERY */ RWP_COEFF, /* block write/read process call for COEFFICIENTS */ W0, /* write zero bytes ("send byte", command only) */ W1, /* write one byte */ R1, /* read one byte */ R2, /* read two byte "word" */ }; enum pmbus_unit_type { /* _undef_ = 0, */ /* for RW2 and R2 types */ VOLTS = 1, AMPERES, MILLISECONDS, DEGREES_C, WATTS, BITS, /* REVISIT: there are quite a few more units: * - mV/uSec for V transition rates * - mV/A (mOhm) for V droop * - Ohms for calibration * - percent for duty cycle and fan speed * - RPM for fan speed * - kHz for frequency * - ... */ /* for various RWB bits of inventory data */ STRING, }; struct pmbus_coefficients { u8 valid; /* Coefficients of the device, valid only after initialization. * * Yeech ... the PMBus spec doesn't talk about signs, except to * say that output voltages are always positive. Here we will * assume that all values include sign bits. */ s8 R; s16 m; s16 b; }; /* * This struct captures the PMBus 1.1 command summary data, in Part II * Appendix I of the spec and updated to include units. It's set up so * it can be easily augmented with device-specfic query data and other * data as needed to work with these commands. */ struct pmbus_cmd_desc { /* data from Part II of PMBus spec (constant) */ const char *tag; u16 cmd; u8 type; u8 units; /* data just for this utility */ u8 flags; #if 0 void (*decode)(struct pmbus_cmd_desc *op, int value); // REVISIT encode too #endif /* from device (variable) */ u8 query; struct pmbus_coefficients c[2]; /* 0 = w, 1 = r */ }; /* some of the command codes found in pmbus_cmd_desc.cmd; * these are specifically recognized in this code. */ #define PMB_CLEAR_FAULT 0x03 #define PMB_CAPABILITY 0x19 #define PMB_QUERY 0x1a #define PMB_COEFFICIENTS 0x30 #define PMB_STATUS_BYTE 0x78 #define PMB_STATUS_WORD 0x79 #define PMB_STATUS_VOUT 0x7a #define PMB_STATUS_IOUT 0x7b #define PMB_STATUS_INPUT 0x7c #define PMB_STATUS_TEMPERATURE 0x7d #define PMB_STATUS_CML 0x7e #define PMB_STATUS_OTHER 0x7f #define PMB_STATUS_MFR_SPECIFIC 0x80 #define PMB_STATUS_FANS_1_2 0x81 #define PMB_STATUS_FANS_3_4 0x82 #define PMB_PMBUS_REVISION 0x98 #define PMB_MFR_ID 0x99 #define PMB_MFR_MODEL 0x9a #define PMB_MFR_REVISION 0x9b #define PMB_MFR_LOCATION 0x9c #define PMB_MFR_DATE 0x9d #define PMB_MFR_SERIAL 0x9e #define PMB_USER_DATA(x) (0xb0 + (x)) /* 0 <= x <= 15 */ #define PMB_MFR_SPECIFIC(x) (0xd0 + (x)) /* 0 <= x <= 45 */ #define PMB_MFR_EXT(x) (0xfe00 + (x)) /* 0 <= x <= 255 */ #define PMB_EXT(x) (0xff00 + (x)) /* 0 <= x <= 255 */ /* flags for pmbus_cmd-desc.flags */ #define FLG_SHOW_P1 (1 << 0) #define FLG_STATUS (1 << 1) static inline int is_pmb_8bit(u16 cmd) { /* pure 8 bit command */ return (cmd & 0xff00) == 0 && (cmd & 0xfe) != 0xfe; } static inline int is_pmb_extended(u16 cmd) { /* 9 bit command, with 2nd (upper) byte */ return (cmd & 0xfe00) == 0xfe00; } /*----------------------------------------------------------------------*/ /* * NOTE: first version expects these static values to be morphed * in place ... maximum of one device per process. To support more * than one PMBus device per process, make this table "const" then * use these as examplars. * * REVISIT more of these should probably have units... */ static struct pmbus_cmd_desc pmbus_ops[] = { /* These are in numeric order, modulo sequence gaps in the PMBus spec. */ { .cmd = 0x00, .tag = "page", .type = RW1, }, { .cmd = 0x01, .tag = "operation", .type = RW1, }, { .cmd = 0x02, .tag = "on_off_config", .type = RW1, }, { .cmd = PMB_CLEAR_FAULT, .tag = "clear_fault", .type = W0, }, { .cmd = 0x04, .tag = "phase", .type = RW1, }, { .cmd = 0x10, .tag = "write_protect", .type = RW1, }, { .cmd = 0x11, .tag = "store_default_all", .type = W0, }, { .cmd = 0x12, .tag = "restore_default_all", .type = W0, }, { .cmd = 0x13, .tag = "store_default_code", .type = W1, }, { .cmd = 0x14, .tag = "restore_default_code", .type = W1, }, { .cmd = 0x15, .tag = "store_user_all", .type = W0, }, { .cmd = 0x16, .tag = "restore_user_all", .type = W0, }, { .cmd = 0x17, .tag = "store_user_code", .type = W1, }, { .cmd = 0x18, .tag = "restore_user_code", .type = W1, }, { .cmd = PMB_CAPABILITY, .tag = "capability", .type = R1, .flags = FLG_SHOW_P1, }, { .cmd = PMB_QUERY, .tag = "query", .type = RWP_QUERY, }, { .cmd = 0x20, .tag = "vout_mode", .type = RW1, }, { .cmd = 0x21, .tag = "vout_command", .type = RW2, }, { .cmd = 0x22, .tag = "vout_trim", .type = RW2, .units = VOLTS, }, { .cmd = 0x23, .tag = "vout_cal_offset", .type = RW2, .units = VOLTS, }, { .cmd = 0x24, .tag = "vout_max", .type = RW2, .units = VOLTS, }, { .cmd = 0x25, .tag = "vout_margin_high", .type = RW2, .units = VOLTS, }, { .cmd = 0x26, .tag = "vout_margin_low", .type = RW2, .units = VOLTS, }, { .cmd = 0x27, .tag = "vout_transition_rate", .type = RW2, }, { .cmd = 0x28, .tag = "vout_droop", .type = RW2, }, { .cmd = 0x29, .tag = "vout_scale_loop", .type = RW2, }, { .cmd = 0x2a, .tag = "vout_scale_monitor", .type = RW2, }, { .cmd = PMB_COEFFICIENTS, .tag = "coefficients", .type = RWP_COEFF, }, { .cmd = 0x31, .tag = "pout_max", .type = RW2, .units = WATTS, }, { .cmd = 0x32, .tag = "max_duty", .type = RW2, }, { .cmd = 0x33, .tag = "frequency_switch", .type = RW2, }, { .cmd = 0x35, .tag = "vin_on", .type = RW2, .units = VOLTS, }, { .cmd = 0x36, .tag = "vin_off", .type = RW2, .units = VOLTS, }, { .cmd = 0x37, .tag = "interleave", .type = RW2, }, { .cmd = 0x38, .tag = "iout_cal_gain", .type = RW2, }, { .cmd = 0x39, .tag = "iout_cal_offset", .type = RW2, .units = AMPERES, }, { .cmd = 0x3a, .tag = "fan_config_1_2", .type = RW1, }, { .cmd = 0x3b, .tag = "fan_command_1", .type = RW2, }, { .cmd = 0x3c, .tag = "fan_command_2", .type = RW2, }, { .cmd = 0x3d, .tag = "fan_config_3_4", .type = RW1, }, { .cmd = 0x3e, .tag = "fan_command_3", .type = RW2, }, { .cmd = 0x3f, .tag = "fan_command_4", .type = RW2, }, { .cmd = 0x40, .tag = "vout_ov_fault_limit", .type = RW2, .units = VOLTS, }, { .cmd = 0x41, .tag = "vout_ov_fault_response", .type = RW1, }, { .cmd = 0x42, .tag = "vout_ov_warn_limit", .type = RW2, .units = VOLTS, }, { .cmd = 0x43, .tag = "vout_uv_warn_limit", .type = RW2, .units = VOLTS, }, { .cmd = 0x44, .tag = "vout_uv_fault_limit", .type = RW2, .units = VOLTS, }, { .cmd = 0x45, .tag = "vout_uv_fault_response", .type = RW1, }, { .cmd = 0x46, .tag = "iout_oc_fault_limit", .type = RW2, .units = AMPERES, }, { .cmd = 0x47, .tag = "iout_oc_fault_response", .type = RW1, }, { .cmd = 0x48, .tag = "iout_oc_lv_fault_limit", .type = RW2, .units = AMPERES, }, { .cmd = 0x49, .tag = "iout_oc_lv_fault_response", .type = RW1, }, { .cmd = 0x4a, .tag = "iout_oc_warn_limit", .type = RW2, .units = AMPERES, }, { .cmd = 0x4b, .tag = "iout_uc_fault_limit", .type = RW2, .units = AMPERES, }, { .cmd = 0x4c, .tag = "iout_uc_fault_response", .type = RW1, }, { .cmd = 0x4f, .tag = "ot_fault_limit", .type = RW2, .units = DEGREES_C,}, { .cmd = 0x50, .tag = "ot_fault_response", .type = RW1, }, { .cmd = 0x51, .tag = "ot_warn_limit", .type = RW2, .units = DEGREES_C,}, { .cmd = 0x52, .tag = "ut_warn_limit", .type = RW2, .units = DEGREES_C,}, { .cmd = 0x53, .tag = "ut_fault_limit", .type = RW2, .units = DEGREES_C,}, { .cmd = 0x54, .tag = "ut_fault_response", .type = RW1, }, { .cmd = 0x55, .tag = "vin_ov_fault_limit", .type = RW2, .units = VOLTS, }, { .cmd = 0x56, .tag = "vin_ov_fault_response", .type = RW1, }, { .cmd = 0x57, .tag = "vin_ov_warn_limit", .type = RW2, .units = VOLTS, }, { .cmd = 0x58, .tag = "vin_uv_warn_limit", .type = RW2, .units = VOLTS, }, { .cmd = 0x59, .tag = "vin_uv_fault_limit", .type = RW2, .units = VOLTS, }, { .cmd = 0x5a, .tag = "vin_uv_fault_response", .type = RW1, }, { .cmd = 0x5b, .tag = "iin_oc_fault_limit", .type = RW2, .units = AMPERES, }, { .cmd = 0x5c, .tag = "iin_oc_fault_response", .type = RW1, }, { .cmd = 0x5d, .tag = "iin_oc_warn_limit", .type = RW2, .units = AMPERES, }, { .cmd = 0x5e, .tag = "power_good_on", .type = RW2, .units = VOLTS, }, { .cmd = 0x5f, .tag = "power_good_off", .type = RW2, .units = VOLTS, }, { .cmd = 0x60, .tag = "ton_delay", .type = RW2, .units = MILLISECONDS, }, { .cmd = 0x61, .tag = "ton_rise", .type = RW2, .units = MILLISECONDS, }, { .cmd = 0x62, .tag = "ton_max_fault_limit", .type = RW2, .units = MILLISECONDS, }, { .cmd = 0x63, .tag = "ton_max_fault_response", .type = RW1, }, { .cmd = 0x64, .tag = "toff_delay", .type = RW2, .units = MILLISECONDS, }, { .cmd = 0x65, .tag = "toff_fall", .type = RW2, .units = MILLISECONDS, }, { .cmd = 0x66, .tag = "toff_max_warn_limit", .type = RW2, .units = MILLISECONDS, }, { .cmd = 0x68, .tag = "pout_op_fault_limit", .type = RW2, .units = WATTS, }, { .cmd = 0x69, .tag = "pout_op_fault_response", .type = RW1, }, { .cmd = 0x6a, .tag = "pout_op_warn_limit", .type = RW2, .units = WATTS, }, { .cmd = 0x6b, .tag = "pin_op_warn_limit", .type = RW2, .units = WATTS, }, { .cmd = PMB_STATUS_BYTE, .tag = "status_byte", .type = R1, .flags = FLG_STATUS, }, { .cmd = PMB_STATUS_WORD, .tag = "status_word", .type = R2, .units = BITS, .flags = FLG_STATUS, }, { .cmd = PMB_STATUS_VOUT, .tag = "status_vout", .type = R1, .flags = FLG_STATUS, }, { .cmd = PMB_STATUS_IOUT, .tag = "status_iout", .type = R1, .flags = FLG_STATUS, }, { .cmd = PMB_STATUS_INPUT, .tag = "status_input", .type = R1, .flags = FLG_STATUS, }, { .cmd = PMB_STATUS_TEMPERATURE, .tag = "status_temperature", .type = R1, .flags = FLG_STATUS, }, { .cmd = PMB_STATUS_CML, .tag = "status_cml", .type = R1, .flags = FLG_STATUS, }, { .cmd = PMB_STATUS_OTHER, .tag = "status_other", .type = R1, .flags = FLG_STATUS, }, { .cmd = PMB_STATUS_MFR_SPECIFIC, .tag = "status_mfr_specific", .type = R1, .flags = FLG_STATUS, }, { .cmd = PMB_STATUS_FANS_1_2, .tag = "status_fans_1_2", .type = R1, .flags = FLG_STATUS, }, { .cmd = PMB_STATUS_FANS_3_4, .tag = "status_fans_3_4", .type = R1, .flags = FLG_STATUS, }, { .cmd = 0x88, .tag = "read_vin", .type = R2, .units = VOLTS, }, { .cmd = 0x89, .tag = "read_iin", .type = R2, .units = AMPERES, }, { .cmd = 0x8a, .tag = "read_vcap", .type = R2, .units = VOLTS, }, { .cmd = 0x8b, .tag = "read_vout", .type = R2, .units = VOLTS, }, { .cmd = 0x8c, .tag = "read_iout", .type = R2, .units = AMPERES, }, { .cmd = 0x8d, .tag = "read_temperature_1", .type = R2, .units = DEGREES_C, }, { .cmd = 0x8e, .tag = "read_temperature_2", .type = R2, .units = DEGREES_C, }, { .cmd = 0x8f, .tag = "read_temperature_3", .type = R2, .units = DEGREES_C, }, { .cmd = 0x90, .tag = "read_fan_speed_1", .type = R2, }, { .cmd = 0x91, .tag = "read_fan_speed_2", .type = R2, }, { .cmd = 0x92, .tag = "read_fan_speed_3", .type = R2, }, { .cmd = 0x93, .tag = "read_fan_speed_4", .type = R2, }, { .cmd = 0x94, .tag = "read_duty_cycle", .type = R2, }, { .cmd = 0x95, .tag = "read_frequency", .type = R2, }, { .cmd = 0x96, .tag = "read_pout", .type = R2, .units = WATTS, }, { .cmd = 0x97, .tag = "read_pin", .type = R2, .units = WATTS, }, { .cmd = PMB_PMBUS_REVISION, .tag = "pmbus_revision", .type = R1, .flags = FLG_SHOW_P1, }, { .cmd = PMB_MFR_ID, .tag = "mfr_id", .type = RWB, .units = STRING, .flags = FLG_SHOW_P1, }, { .cmd = PMB_MFR_MODEL, .tag = "mfr_model", .type = RWB, .units = STRING, .flags = FLG_SHOW_P1, }, { .cmd = PMB_MFR_REVISION, .tag = "mfr_revision", .type = RWB, .units = STRING, .flags = FLG_SHOW_P1, }, { .cmd = PMB_MFR_LOCATION, .tag = "mfr_location", .type = RWB, .units = STRING, .flags = FLG_SHOW_P1, }, { .cmd = PMB_MFR_DATE, .tag = "mfr_date", .type = RWB, .units = STRING, .flags = FLG_SHOW_P1, }, { .cmd = PMB_MFR_SERIAL, .tag = "mfr_serial", .type = RWB, .units = STRING, .flags = FLG_SHOW_P1, }, { .cmd = 0xa0, .tag = "mfr_vin_min", .type = R2, .units = VOLTS, }, { .cmd = 0xa1, .tag = "mfr_vin_max", .type = R2, .units = VOLTS, }, { .cmd = 0xa2, .tag = "mfr_iin_max", .type = R2, .units = AMPERES, }, { .cmd = 0xa3, .tag = "mfr_pin_max", .type = R2, .units = WATTS, }, { .cmd = 0xa4, .tag = "mfr_vout_min", .type = R2, .units = VOLTS, }, { .cmd = 0xa5, .tag = "mfr_vout_max", .type = R2, .units = VOLTS, }, { .cmd = 0xa5, .tag = "mfr_iout_max", .type = R2, .units = AMPERES, }, { .cmd = 0xa7, .tag = "mfr_pout_max", .type = R2, .units = WATTS, }, { .cmd = 0xa8, .tag = "mfr_tambient_max", .type = R2, .units = DEGREES_C, }, { .cmd = 0xa9, .tag = "mfr_tambient_min", .type = R2, .units = DEGREES_C, }, { .cmd = 0xaa, .tag = "mfr_efficiency_ll", .type = RWB14, }, { .cmd = 0xab, .tag = "mfr_efficiency_hl", .type = RWB14, }, { .cmd = PMB_USER_DATA(0), .tag = "user_data_00", .type = RWB, }, { .cmd = PMB_USER_DATA(1), .tag = "user_data_01", .type = RWB, }, { .cmd = PMB_USER_DATA(2), .tag = "user_data_02", .type = RWB, }, { .cmd = PMB_USER_DATA(3), .tag = "user_data_03", .type = RWB, }, { .cmd = PMB_USER_DATA(4), .tag = "user_data_04", .type = RWB, }, { .cmd = PMB_USER_DATA(5), .tag = "user_data_05", .type = RWB, }, { .cmd = PMB_USER_DATA(6), .tag = "user_data_06", .type = RWB, }, { .cmd = PMB_USER_DATA(7), .tag = "user_data_07", .type = RWB, }, { .cmd = PMB_USER_DATA(8), .tag = "user_data_08", .type = RWB, }, { .cmd = PMB_USER_DATA(9), .tag = "user_data_09", .type = RWB, }, { .cmd = PMB_USER_DATA(10), .tag = "user_data_10", .type = RWB, }, { .cmd = PMB_USER_DATA(11), .tag = "user_data_11", .type = RWB, }, { .cmd = PMB_USER_DATA(12), .tag = "user_data_12", .type = RWB, }, { .cmd = PMB_USER_DATA(13), .tag = "user_data_13", .type = RWB, }, { .cmd = PMB_USER_DATA(14), .tag = "user_data_14", .type = RWB, }, { .cmd = PMB_USER_DATA(15), .tag = "user_data_15", .type = RWB, }, { .cmd = PMB_MFR_SPECIFIC(0), .tag = "mfr_specific_00", }, { .cmd = PMB_MFR_SPECIFIC(1), .tag = "mfr_specific_01", }, { .cmd = PMB_MFR_SPECIFIC(2), .tag = "mfr_specific_02", }, { .cmd = PMB_MFR_SPECIFIC(3), .tag = "mfr_specific_03", }, { .cmd = PMB_MFR_SPECIFIC(4), .tag = "mfr_specific_04", }, { .cmd = PMB_MFR_SPECIFIC(5), .tag = "mfr_specific_05", }, { .cmd = PMB_MFR_SPECIFIC(6), .tag = "mfr_specific_06", }, { .cmd = PMB_MFR_SPECIFIC(7), .tag = "mfr_specific_07", }, { .cmd = PMB_MFR_SPECIFIC(8), .tag = "mfr_specific_08", }, { .cmd = PMB_MFR_SPECIFIC(9), .tag = "mfr_specific_09", }, { .cmd = PMB_MFR_SPECIFIC(10), .tag = "mfr_specific_10", }, { .cmd = PMB_MFR_SPECIFIC(11), .tag = "mfr_specific_11", }, { .cmd = PMB_MFR_SPECIFIC(12), .tag = "mfr_specific_12", }, { .cmd = PMB_MFR_SPECIFIC(13), .tag = "mfr_specific_13", }, { .cmd = PMB_MFR_SPECIFIC(14), .tag = "mfr_specific_14", }, { .cmd = PMB_MFR_SPECIFIC(15), .tag = "mfr_specific_15", }, { .cmd = PMB_MFR_SPECIFIC(16), .tag = "mfr_specific_16", }, { .cmd = PMB_MFR_SPECIFIC(17), .tag = "mfr_specific_17", }, { .cmd = PMB_MFR_SPECIFIC(18), .tag = "mfr_specific_18", }, { .cmd = PMB_MFR_SPECIFIC(19), .tag = "mfr_specific_19", }, { .cmd = PMB_MFR_SPECIFIC(20), .tag = "mfr_specific_20", }, { .cmd = PMB_MFR_SPECIFIC(21), .tag = "mfr_specific_21", }, { .cmd = PMB_MFR_SPECIFIC(22), .tag = "mfr_specific_22", }, { .cmd = PMB_MFR_SPECIFIC(23), .tag = "mfr_specific_23", }, { .cmd = PMB_MFR_SPECIFIC(24), .tag = "mfr_specific_24", }, { .cmd = PMB_MFR_SPECIFIC(25), .tag = "mfr_specific_25", }, { .cmd = PMB_MFR_SPECIFIC(26), .tag = "mfr_specific_26", }, { .cmd = PMB_MFR_SPECIFIC(27), .tag = "mfr_specific_27", }, { .cmd = PMB_MFR_SPECIFIC(29), .tag = "mfr_specific_28", }, { .cmd = PMB_MFR_SPECIFIC(29), .tag = "mfr_specific_29", }, { .cmd = PMB_MFR_SPECIFIC(30), .tag = "mfr_specific_30", }, { .cmd = PMB_MFR_SPECIFIC(31), .tag = "mfr_specific_31", }, { .cmd = PMB_MFR_SPECIFIC(32), .tag = "mfr_specific_32", }, { .cmd = PMB_MFR_SPECIFIC(33), .tag = "mfr_specific_33", }, { .cmd = PMB_MFR_SPECIFIC(34), .tag = "mfr_specific_34", }, { .cmd = PMB_MFR_SPECIFIC(35), .tag = "mfr_specific_35", }, { .cmd = PMB_MFR_SPECIFIC(36), .tag = "mfr_specific_36", }, { .cmd = PMB_MFR_SPECIFIC(37), .tag = "mfr_specific_37", }, { .cmd = PMB_MFR_SPECIFIC(38), .tag = "mfr_specific_38", }, { .cmd = PMB_MFR_SPECIFIC(39), .tag = "mfr_specific_39", }, { .cmd = PMB_MFR_SPECIFIC(40), .tag = "mfr_specific_40", }, { .cmd = PMB_MFR_SPECIFIC(41), .tag = "mfr_specific_41", }, { .cmd = PMB_MFR_SPECIFIC(42), .tag = "mfr_specific_42", }, { .cmd = PMB_MFR_SPECIFIC(43), .tag = "mfr_specific_43", }, { .cmd = PMB_MFR_SPECIFIC(44), .tag = "mfr_specific_44", }, { .cmd = PMB_MFR_SPECIFIC(45), .tag = "mfr_specific_45", }, { .cmd = 0xfe, .tag = "mfr_specific_command_ext", }, { .cmd = 0xff, .tag = "pmbus_command_ext", }, { /* ZEROES TERMINATE THIS LIST */ }, }; static struct pmbus_cmd_desc unsupported = { .tag = "UNSUPPORTED", }; /*----------------------------------------------------------------------*/ struct pmbus_dev { int fd; unsigned long funcs; char *bus; u8 addr; /* on bus */ u8 revision; u8 capability; u8 no_query; u8 use_pec; struct pmbus_cmd_desc *op[256]; }; static int verbose; static int enable_pec; /*----------------------------------------------------------------------*/ /* * The only userspace code for SMBus ops I found comes with a libsensors * package that clobbers <linux/i2c-dev.h> on install. And what we need * is PMBus ops anyway... hence this code, which understands some of the * ways that PMBus differs from SMBus. * * Use of 2-byte commands is rejected, but comments show how it could * be implemented with reasonable portability. * * REVISIT return values handling ... "errno" seems to be meaningless * since it's almost always EPERM (1), sigh. */ /* Send a bit to the device, if it's present. */ static inline int smbus_quick(int fd, int flag) { struct i2c_smbus_ioctl_data arg; memset(&arg, 0, sizeof arg); arg.read_write = flag ? I2C_SMBUS_READ : I2C_SMBUS_WRITE; /* no command */ arg.size = I2C_SMBUS_QUICK; /* no data */ if (ioctl(fd, I2C_SMBUS, &arg) < 0) return -errno; return 0; } /*----------------------------------------------------------------------*/ /* * READ operations */ /* Returns a byte, or negative errno. */ static int pmbus_read_byte_data(int fd, u16 cmd) { struct i2c_smbus_ioctl_data arg; u8 byte; /* use i2c; or READ_I2C_BLOCK_2: 2 byte cmd, 1 byte block */ if (is_pmb_extended(cmd)) return -ENOSYS; if (!is_pmb_8bit(cmd)) return -EINVAL; memset(&arg, 0, sizeof arg); arg.read_write = I2C_SMBUS_READ; arg.command = cmd; arg.size = I2C_SMBUS_BYTE_DATA; arg.data = (union i2c_smbus_data *) &byte; if (ioctl(fd, I2C_SMBUS, &arg) < 0) return -errno; return byte; } /* Returns a word, or negative errno. */ static int pmbus_read_word_data(int fd, u16 cmd) { struct i2c_smbus_ioctl_data arg; u16 word; /* use i2c; or READ_I2C_BLOCK_2: 2 byte cmd, 2 byte block */ if (is_pmb_extended(cmd)) return -ENOSYS; if (!is_pmb_8bit(cmd)) return -EINVAL; memset(&arg, 0, sizeof arg); arg.read_write = I2C_SMBUS_READ; arg.command = cmd; arg.size = I2C_SMBUS_WORD_DATA; arg.data = (union i2c_smbus_data *) &word; if (ioctl(fd, I2C_SMBUS, &arg) < 0) return -errno; /* adapter code handled byteswapping if needed */ return word; } /* Returns the number of bytes copied into read_buf, or negative errno. * If the block is bigger than read_len, read_len is copied and -E2BIG * is returned (so the caller can recover, somewhat). */ static int pmbus_read_block(struct pmbus_dev *pmdev, u16 cmd, unsigned read_len, u8 *read_buf) { struct i2c_smbus_ioctl_data arg; union i2c_smbus_data data; int retval; int len; if (!read_buf || read_len == 0) return -EINVAL; /* use i2c; or READ_I2C_BLOCK_2: 2 byte cmd, N byte block */ if (is_pmb_extended(cmd)) return -ENOSYS; if (!is_pmb_8bit(cmd)) return -EINVAL; /* Handle "large" blocks sanely by issuing an extra read to * prevent one fault-path traversal (e.g. SMBALERT#) when the * block is bigger than the morsel allowed by SMBus. */ len = pmbus_read_byte_data(pmdev->fd, cmd); if (len < 0) return len; if (len > I2C_SMBUS_BLOCK_MAX) { retval = -EFBIG; goto try_i2c; } /* If there's no SMBus support, I2C will work (we checked). * Or, we might use "I2C block read" for up to 31 data bytes. */ if (!(pmdev->funcs & I2C_FUNC_SMBUS_READ_BLOCK_DATA)) { retval = -EOPNOTSUPP; goto try_i2c; } memset(&arg, 0, sizeof arg); arg.read_write = I2C_SMBUS_READ; arg.command = cmd; arg.size = I2C_SMBUS_BLOCK_DATA; arg.data = &data; /* When this fails, we can't really know why. In case it's the * SMBus code saying "block too big", try again (if possible). */ if (ioctl(pmdev->fd, I2C_SMBUS, &arg) < 0) { retval = -errno; goto try_i2c; } if (data.block[0] <= read_len) { if (data.block[0] > 32) { /* NOTE: this probably won't be visible */ retval = -EFBIG; goto try_i2c; } retval = read_len = data.block[0]; } else retval = -E2BIG; memcpy(read_buf, &data.block[1], read_len); return retval; try_i2c: /* NOTE: no PEC here, but it *could* be done here in userspace */ if (pmdev->funcs & I2C_FUNC_I2C) { struct i2c_msg msg[2]; struct i2c_rdwr_ioctl_data msgdat; u8 buf[256]; msgdat.msgs = msg; msgdat.nmsgs = 2; msg[0].addr = msg[1].addr = pmdev->addr; buf[0] = cmd & 0x0ff; msg[0].flags = 0; msg[0].len = 1; msg[0].buf = buf; msg[1].flags = I2C_M_RD; msg[1].len = len + 1; msg[1].buf = buf; if (ioctl(pmdev->fd, I2C_RDWR, &msgdat) < 0) return -errno; if (buf[0] <= read_len) retval = read_len = buf[0]; else retval = -E2BIG; memcpy(read_buf, &buf[1], read_len); } return retval; } /*----------------------------------------------------------------------*/ /* * WRITE operations */ /* This is ONLY to temporarily shut up some gcc warnings; most * of the write-side infrastructure is not yet used here. */ #define SHADDAP inline /* With PMBus, transactions may never start with the "read" bit set. * That includes "Quick" messages. Break that rule and get a CML * alert (e.g. SMBALERT#). */ static int pmbus_quick(int fd) { return smbus_quick(fd, 0); } /* Returns zero, or negative errno. */ static int smbus_write_byte(int fd, u8 byte) { struct i2c_smbus_ioctl_data arg; memset(&arg, 0, sizeof arg); arg.read_write = I2C_SMBUS_WRITE; arg.command = byte; arg.size = I2C_SMBUS_BYTE; if (ioctl(fd, I2C_SMBUS, &arg) < 0) return -errno; return 0; } /* Returns zero, or negative errno. */ static SHADDAP int pmbus_write_byte_data(int fd, u16 cmd, u8 byte) { struct i2c_smbus_ioctl_data arg; /* use i2c; or WRITE_I2C_BLOCK_2: 2 byte cmd, 1 byte block; * or tweak params to SMBus "write word" */ if (is_pmb_extended(cmd)) return -ENOSYS; if (!is_pmb_8bit(cmd)) return -EINVAL; memset(&arg, 0, sizeof arg); arg.read_write = I2C_SMBUS_WRITE; arg.command = cmd; arg.size = I2C_SMBUS_BYTE_DATA; arg.data = (union i2c_smbus_data *) &byte; if (ioctl(fd, I2C_SMBUS, &arg) < 0) return -errno; return 0; } /* Returns zero, or negative errno. */ static SHADDAP int pmbus_write_word_data(int fd, u16 cmd, u16 word) { struct i2c_smbus_ioctl_data arg; /* use i2c; or WRITE_I2C_BLOCK_2: 2 byte cmd, 2 byte block */ if (is_pmb_extended(cmd)) return -ENOSYS; if (!is_pmb_8bit(cmd)) return -EINVAL; /* adapter code handles byteswapping if needed */ memset(&arg, 0, sizeof arg); arg.read_write = I2C_SMBUS_WRITE; arg.command = cmd; arg.size = I2C_SMBUS_WORD_DATA; arg.data = (union i2c_smbus_data *) &word; if (ioctl(fd, I2C_SMBUS, &arg) < 0) return -errno; return 0; } /* Returns zero, or negative errno. */ static SHADDAP int pmbus_write_block(struct pmbus_dev *pmdev, u16 cmd, unsigned write_len, u8 *write_buf) { struct i2c_smbus_ioctl_data arg; union i2c_smbus_data data; int retval; if (!write_buf || write_len == 0 || write_len > 255) return -EINVAL; /* use i2c; or WRITE_I2C_BLOCK_2: 2 byte cmd, N byte block */ if (is_pmb_extended(cmd)) return -ENOSYS; if (!is_pmb_8bit(cmd)) return -EINVAL; /* If there's no SMBus support, I2C might work (we did NOT ensure * there will be one or the other) */ if (!(pmdev->funcs & I2C_FUNC_SMBUS_WRITE_BLOCK_DATA)) { retval = -EOPNOTSUPP; goto try_i2c; } if (write_len > 32) { retval = -EFBIG; goto try_i2c; } data.block[0] = write_len; memcpy(&data.block[1], write_buf, write_len); memset(&arg, 0, sizeof arg); arg.read_write = I2C_SMBUS_WRITE; arg.command = cmd; arg.size = I2C_SMBUS_BLOCK_DATA; arg.data = &data; if (ioctl(pmdev->fd, I2C_SMBUS, &arg) < 0) return -errno; return 0; try_i2c: /* NOTE: no PEC here, but it *could* be done here in userspace */ if (pmdev->funcs & I2C_FUNC_I2C) { struct i2c_msg msg; struct i2c_rdwr_ioctl_data msgdat; u8 buf[257]; msgdat.msgs = &msg; msgdat.nmsgs = 1; msg.addr = pmdev->addr; msg.flags = 0; msg.len = 2 + write_len; msg.buf = buf; buf[0] = cmd; buf[1] = write_len; memcpy(&buf[2], write_buf, write_len); retval = (ioctl(pmdev->fd, I2C_RDWR, &msgdat) < 0) ? -errno : 0; } return retval; } /*----------------------------------------------------------------------*/ static void coefficients(struct pmbus_dev *pmdev, struct pmbus_cmd_desc *op, int read) { union i2c_smbus_data data; int status; struct pmbus_coefficients *c; read = !!read; c = op->c + read; /* This is specified as a block proc call, which is currently not * widely supported. The I2C-level backup makes sure that many * more systems can use this call. * * NOTE as with QUERY, handling for "extended" commands is not * specified by PMBus 1.1 ... */ if (pmdev->funcs & I2C_FUNC_SMBUS_BLOCK_PROC_CALL) { struct i2c_smbus_ioctl_data arg; data.block[0] = 2; data.block[1] = op->cmd; data.block[2] = read; memset(&arg, 0, sizeof arg); arg.read_write = I2C_SMBUS_WRITE; arg.command = PMB_COEFFICIENTS; arg.size = I2C_SMBUS_BLOCK_PROC_CALL; arg.data = &data; status = ioctl(pmdev->fd, I2C_SMBUS, &arg); /* NOTE: no PEC here, but it *could* be done here in userspace */ } else if (pmdev->funcs & I2C_FUNC_I2C) { struct i2c_msg msg[2]; struct i2c_rdwr_ioctl_data msgdat; msgdat.msgs = msg; msgdat.nmsgs = 2; msg[0].addr = msg[1].addr = pmdev->addr; msg[0].flags = 0; msg[0].len = 4; msg[0].buf = data.block; data.block[0] = PMB_COEFFICIENTS; data.block[1] = 2; data.block[2] = op->cmd; data.block[3] = read; msg[1].flags = I2C_M_RD; msg[1].len = 6; msg[1].buf = data.block; status = ioctl(pmdev->fd, I2C_RDWR, &msgdat); } else status = -EOPNOTSUPP; if (status < 0) return; if (data.block[0] != 5) return; c->m = (data.block[2] << 8) | data.block[1]; c->b = (data.block[4] << 8) | data.block[3]; c->R = data.block[5]; c->valid = 1; } static void query(struct pmbus_dev *pmdev, struct pmbus_cmd_desc *op) { struct i2c_smbus_ioctl_data arg; u16 word; /* NOTE query for "extended" commands is not specified by PMBus 1.1; * presumably that will just send a two byte block (which can't use * the portability tweak below). Fortunately there's no evident * need to support such two-byte commands yet. */ if (op->cmd > 0xff) return; /* This is specified as a block proc call, which is currently not * widely supported. Instead, implement this using normal proc * calls, which are more widely available. */ word = (op->cmd << 8) | 1; memset(&arg, 0, sizeof arg); arg.command = PMB_QUERY; arg.size = I2C_SMBUS_PROC_CALL; arg.data = (union i2c_smbus_data *) &word; if (ioctl(pmdev->fd, I2C_SMBUS, &arg) < 0 || (word & 0x00ff) != 1) { /* REVISIT we _really_ want QUERY to work, so it'd be nice * to recover from transient faults here. If we could tell * such faults from real ones, that is... instead of seeing * false EPERM reports for everything in the I2C stack. */ pmdev->no_query = 1; return; } word >>= 8; /* query supported, but not this operation? */ if (!(word & (1 << 7))) { pmdev->op[op->cmd] = &unsupported; return; } /* NOTE eventually use copy/clone here instead of in-place mutate, * when managing multiple devices concurrently */ op->query = word; pmdev->op[op->cmd] = op; /* Try to get the coefficients for DIRECT format numbers */ if (((word >> 2) & 7) == 3 && pmdev->op[PMB_COEFFICIENTS]) { if (word & (1 << 5)) /* read (always) */ coefficients(pmdev, op, 1); if (word & (1 << 6)) /* write */ coefficients(pmdev, op, 0); } } /* Return: negative = can't tell, 0 = no, 1 = yes */ static int checksupport(struct pmbus_dev *pmdev, u16 cmd) { /* NOTE: no revision check. QUERY is a PMBUS 1.1 addition, * but the device's PMBus revision may not be exposed. * * NOTE: this assumes we can check prefixes for PMB_MFR_EXT(x) * and PMB_EXT(x) operations, even though we can't query those * operations directly... */ if (is_pmb_extended(cmd)) return -1; if (pmdev->op[PMB_QUERY] == &unsupported || pmdev->no_query) return -1; if (!pmdev->op[cmd]) { struct pmbus_cmd_desc *op = pmbus_ops; for (op = pmbus_ops; !pmdev->no_query && op->tag; op++) { if (op->cmd == cmd) { query(pmdev, op); break; } } } return pmdev->op[cmd] != &unsupported; } static char *pmbus_read_string(struct pmbus_dev *pmdev, u16 cmd) { u8 buf[256]; int status; /* For non-queryable devices we'll still try to read inventory * data strings. That should be harmless but informative. */ if (checksupport(pmdev, cmd) == 0) return NULL; memset(buf, 0, sizeof buf); status = pmbus_read_block(pmdev, cmd, sizeof buf - 1, buf); return (status > 0) ? strdup((void *)buf) : NULL; } /*----------------------------------------------------------------------*/ static char *units(struct pmbus_cmd_desc *op) { switch (op->units) { case VOLTS: return "Volts"; case AMPERES: return "Amperes"; case MILLISECONDS: return "milliseconds"; case DEGREES_C: return "degrees Celsius"; case WATTS: return "Watts"; default: return NULL; } } /*----------------------------------------------------------------------*/ static void pmbus_list_inventory(struct pmbus_dev *pmdev) { char *mfr = pmbus_read_string(pmdev, PMB_MFR_ID); char *model = pmbus_read_string(pmdev, PMB_MFR_MODEL); char *revision = pmbus_read_string(pmdev, PMB_MFR_REVISION); char *location = pmbus_read_string(pmdev, PMB_MFR_LOCATION); char *date = pmbus_read_string(pmdev, PMB_MFR_DATE); char *serial = pmbus_read_string(pmdev, PMB_MFR_SERIAL); if (!mfr && !model && !revision && !location && !date && !serial) return; /* REVISIT might be useful to fetch all these strings and save * them away. They're the first handle we have on just what * kind of device we're working with... */ printf("Inventory Data:\n"); if (mfr) { printf(" Manufacturer:\t\t%s\n", mfr); free(mfr); } if (model) { printf(" Model:\t\t%s\n", model); free(model); } if (mfr) { printf(" Revision:\t\t%s\n", revision); free(revision); } if (location) { printf(" Built at:\t\t%s\n", location); free(location); } if (date) { printf(" Built on:\t\t%s\n", date); free(date); } if (serial) { printf(" Serial:\t\t%s\n", serial); free(serial); } printf("\n"); } static void pmbus_dev_show_p1(struct pmbus_dev *pmdev) { const char *s0, *s1; struct pmbus_cmd_desc *op; printf("PMBus slave on %s, address %#02x\n\n", pmdev->bus, pmdev->addr); pmbus_list_inventory(pmdev); /* * NOTE spec bug: table shows two four bit masks, which are * described as two fields, 3 bits 7-5 and 5 bits 4-0 ... in * fact part II probably deserves an extra bit. Rev 1.0 of the * spec said the fields are 3 and 5 bits; let's be compatible. */ switch ((pmdev->revision >> 5) & 0x07) { case 0: s1 = "1.0"; break; case 1: s1 = "1.1"; break; default: s1 = "?"; break; } switch (pmdev->revision & 0x1f) { case 0: s0 = "1.0"; break; case 1: s0 = "1.1"; break; default: s0 = "?"; break; } printf("PMBus revisions (%#02x):\tpart I, ver %s; part II, ver %s\n", pmdev->revision, s1, s0); if (pmdev->capability & 0xf0) { switch ((pmdev->capability >> 5) & 3) { case 0: s0 = "100 KHz"; break; case 1: s0 = "400 KHz"; break; default: s0 = "?speed?"; break; } printf("Capabilities (%#02x):\t%s%s%s\n", pmdev->capability, (pmdev->capability & (1 << 7)) ? "PEC, " : "", (pmdev->capability & (1 << 4)) ? "SMBALERT#, ": "", s0); } printf("\n"); if (pmdev->no_query) { printf("Device can't QUERY for supported commands\n"); return; } for (op = pmbus_ops; !pmdev->no_query && op->tag; op++) query(pmdev, op); } /*----------------------------------------------------------------------*/ static void showbits(u16 mask, int i, char *bits[]) { int comma = 0; while (i-- > 0) { if (!(mask & (1 << i))) continue; printf("%s%s", comma++ ? ", " : "", bits[i] ? : "?"); } } void status_byte(struct pmbus_dev *pmdev, u16 cmd, char *label, char *bits[]) { int value; int mode; mode = checksupport(pmdev, cmd); if (mode == 0) return; value = pmbus_read_byte_data(pmdev->fd, cmd); if (value < 0) { if (mode == 1) printf(" ** Device failed read of STATUS_%s?\n", label); return; } printf(" %-21s %02x: ", label, value); showbits(value, 8, bits); printf("\n"); } static void show_status_vout(struct pmbus_dev *pmdev) { static char *bits[8] = { /* FIXME list the bit labels */ }; status_byte(pmdev, PMB_STATUS_VOUT, "VOUT", bits); } static void show_status_iout(struct pmbus_dev *pmdev) { static char *bits[8] = { /* FIXME list the bit labels */ }; status_byte(pmdev, PMB_STATUS_IOUT, "IOUT", bits); } static void show_status_input(struct pmbus_dev *pmdev) { static char *bits[8] = { /* FIXME list the bit labels */ }; status_byte(pmdev, PMB_STATUS_INPUT, "INPUT", bits); } static void show_status_mfr_specific(struct pmbus_dev *pmdev) { static char *bits[8] = { "mfr_status_0", "mfr_status_1", "mfr_status_2", "mfr_status_3", "mfr_status_4", "mfr_status_5", "mfr_status_6", "mfr_status_7", }; status_byte(pmdev, PMB_STATUS_MFR_SPECIFIC, "MFR_SPECIFIC", bits); } static void show_status_fans(struct pmbus_dev *pmdev) { static char *bits1_2[8] = { "airflow warning", "airflow fault", "fan 2 speed override", "fan 1 speed override", "fan 2 warning", "fan 1 warning", "fan 2 fault", "fan 1 fault", }; static char *bits3_4[8] = { "(reserved)", "(reserved)", "fan 4 speed override", "fan 3 speed override", "fan 4 warning", "fan 4 warning", "fan 3 fault", "fan 3 fault", }; status_byte(pmdev, PMB_STATUS_FANS_1_2, "FANS_1_2", bits1_2); status_byte(pmdev, PMB_STATUS_FANS_3_4, "FANS_3_4", bits3_4); } static void show_status_other(struct pmbus_dev *pmdev) { static char *bits[8] = { /* FIXME list the bit labels */ }; status_byte(pmdev, PMB_STATUS_OTHER, "OTHER", bits); } static void show_status_temperature(struct pmbus_dev *pmdev) { static char *bits[8] = { "(reserved)", "(reserved)", "(reserved)", "(reserved)", "overtemp warning", "overtemp fault", "undertemp warning", "undertemp fault", }; status_byte(pmdev, PMB_STATUS_TEMPERATURE, "TEMPERATURE", bits); } static void show_status_cml(struct pmbus_dev *pmdev) { static char *bits[8] = { "other memory/logic fault", "other comm fault", "(reserved)", "processor fault", "memory fault", "PEC", "invalid data", "invalid command", }; status_byte(pmdev, PMB_STATUS_CML, "CML", bits); } static void pmbus_dev_show_status(struct pmbus_dev *pmdev) { static char *bits[16] = { "unspecified", "comm/memory/logic", "temperature", "vin_underflow", "iout_overflow", "vout_overflow", "off", "busy", "unknown", "other", "fan", "power_good#", "mfr", "vin", "iout", "vout", }; int value = -EINVAL; int mode; /* prefer full status word if it's available */ mode = checksupport(pmdev, PMB_STATUS_WORD); if (mode != 0) { value = pmbus_read_word_data(pmdev->fd, PMB_STATUS_WORD); if (mode == 1 && value < 0) { printf(" ** Device failed read of STATUS_%s?\n", "WORD"); return; } if (value >= 0) printf("Status %04x: ", value); } /* else use status byte */ if (value < 0) { mode = checksupport(pmdev, PMB_STATUS_BYTE); if (mode != 0) { value = pmbus_read_byte_data(pmdev->fd, PMB_STATUS_BYTE); if (value < 0) { if (mode == 1) printf(" ** Device failed read" "of STATUS_%s?\n", "BYTE"); return; } printf("Status %02x: ", value); } } showbits(value, 16, bits); printf("\n"); if (value & ((1 << 15) | (1 << 5))) show_status_vout(pmdev); if (value & ((1 << 14) | (1 << 4))) show_status_iout(pmdev); if (value & ((1 << 13) | (1 << 3))) show_status_input(pmdev); if (value & (1 << 12)) show_status_mfr_specific(pmdev); if (value & (1 << 10)) show_status_fans(pmdev); if (value & (1 << 9)) show_status_other(pmdev); if (value & (1 << 2)) show_status_temperature(pmdev); if (value & (1 << 1)) show_status_cml(pmdev); printf("\n"); } /*----------------------------------------------------------------------*/ static void pmbus_dev_show_commands(struct pmbus_dev *pmdev) { unsigned i; struct pmbus_cmd_desc *op; printf("Supported Commands:\n"); for (i = 0; i < 255; i++) { char *format; int direct = 0; op = pmdev->op[i]; if (op == &unsupported || !op) continue; /* command inputs and outputs */ switch (op->type) { case W0: format = "nodata"; break; case RW1: case W1: case R1: format = "u8 (bitmask)"; break; case RW2: case R2: switch ((op->query >> 2) & 7) { case 0: if (op->units == BITS) format = "u16 (bitmask)"; else format = "s16 (LINEAR)"; break; case 3: format = "s16 (DIRECT)"; direct = 1; break; case 5: format = "u16 (VID)"; break; case 6: format = "x16 (MFR)"; break; default: format = "x16 (UNKNOWN)"; break; } break; case RWB: case RWB14: format = "block"; break; case RWP_QUERY: case RWP_COEFF: format = "process_call"; break; default: format = "(UNKNOWN call syntax)"; break; } /* Now display it all */ printf(" %02x %-25s %c%c %s", op->cmd, op->tag, (op->query & (1 << 5)) ? 'r' : ' ', (op->query & (1 << 6)) ? 'w' : ' ', format); format = units(op); if (format == NULL && op->units == STRING) format = "ISO 8859/1 string"; if (format) printf(", %s", format); printf("\n"); /* dump coefficients; "always" R, maybe W too */ if (direct && (op->c[1].valid || op->c[0].valid)) { printf(" Coefficients: "); if (op->c[1].valid) /* Read */ printf("READ b=%d m=%d R=%d", op->c[1].b, op->c[1].m, op->c[1].R); else printf("no READ coefficients?"); if (op->c[0].valid) /* Write */ printf("; WRITE b=%d m=%d R=%d", op->c[0].b, op->c[0].m, op->c[0].R); printf("\n"); } } } static void pmbus_dev_show_values(struct pmbus_dev *pmdev) { unsigned i; struct pmbus_cmd_desc *op; printf("Attribute Values:\n"); for (i = 0; i < 255; i++) { int value; const char *name; op = pmdev->op[i]; if (op == &unsupported || !op) continue; if (op->flags & (FLG_SHOW_P1|FLG_STATUS)) continue; name = op->tag; if (strncmp(name, "read_", 5) == 0) name += 5; /* read and display values, where that's meaningful */ switch (op->type) { case W0: case W1: case RWP_QUERY: case RWP_COEFF: default: /* write-only or magic */ continue; case RW1: case R1: value = pmbus_read_byte_data(pmdev->fd, op->cmd); if (value < 0) { /* FIXME display a diagnostic */ continue; } printf(" %-21s %02x: ", name, value); /* FIXME need per-op (bitmask) decoders... */ printf("(BITMAP)"); printf("\n"); continue; case RW2: case R2: value = pmbus_read_word_data(pmdev->fd, op->cmd); if (value < 0) { /* FIXME display a diagnostic */ continue; } printf(" %-21s %04x: ", name, value); /* FIXME need decoders */ switch ((op->query >> 2) & 7) { case 0: if (op->units == BITS) { printf("(BITMAP)"); } else { double d = value & 0x03ff; /* LINEAR encoding: * - 11 LSBs are signed mantissa * - 5 LSBs are signed exponent */ if (value & 0x0400) d = -d; if (value & 0x8000) d /= 1 << ((value >> 11) & 0x0f); else if (value & 0x7100) d *= 1 << (value >> 11); printf("%g", d); } break; case 3: { double d; int r; /* DIRECT encoding: * X = ((value * (10 ^ -R)) - b) / m */ d = (s16) value; /* ideally: * d *= exp10((double)-op->c[1].R); * but that, or pow(), can be unavailable */ r = op->c[1].R; if (r < 0) { do { d *= 10.0; r++; } while (r < 0); } else if (r > 0) { do { d /= 10.0; r--; } while (r > 0); } d -= (double)op->c[1].b; d /= (double)op->c[1].m; printf("%g", d); } break; case 5: printf("u16 (VID)"); break; case 6: printf("manufacturer specific"); break; default: printf("unknown format"); break; } break; #if 0 // RWB, /* read/write block (up to 255 bytes) */ // RWB14, /* read/write block (of 14 bytes) */ case RWB: case RWB14: format = "block"; #endif break; } name = units(op); if (name) printf(" %s", name); printf("\n"); } printf("\n"); } static void pmbus_dev_show(struct pmbus_dev *pmdev, bool values, bool cmds) { pmbus_dev_show_p1(pmdev); if (values) { pmbus_dev_show_status(pmdev); pmbus_dev_show_values(pmdev); } if (cmds) pmbus_dev_show_commands(pmdev); } /*----------------------------------------------------------------------*/ static void pmbus_clear_fault(struct pmbus_dev *pmdev) { /* if we know we can't clear faults, don't try */ if (checksupport(pmdev, PMB_CLEAR_FAULT) != 0) (void) smbus_write_byte(pmdev->fd, PMB_CLEAR_FAULT); } /*----------------------------------------------------------------------*/ static int pmbus_dev_scan(struct pmbus_dev *pmdev) { int status; /* SMBus (hence PMBus) devices must always ack their addresses. */ if (pmdev->funcs & I2C_FUNC_SMBUS_QUICK) { status = pmbus_quick(pmdev->fd); if (status < 0) { fprintf(stderr, "No device present? error %d\n", status); return status; } } /* QUERY lets us see what operations this device supports, which is * the most interesting information available without product docs. * * It also lets us avoid making calls we know will fail, which is * polite since such failures will trigger host notification or * SMBALERT# on some devices. If we can't query, we'll just hope * those mechanisms aren't in use. */ checksupport(pmdev, PMB_QUERY); if (checksupport(pmdev, PMB_CAPABILITY) != 0) { status = pmbus_read_byte_data(pmdev->fd, PMB_CAPABILITY); if (status < 0) { if (verbose) fprintf(stderr, "No PMBus capability support; " "assuming no PEC, etc\n"); } else { pmdev->capability = status; /* enable PEC if the device supports it */ if ((status & (1 << 7)) && enable_pec) { if (ioctl(pmdev->fd, I2C_PEC, 1) < 0) fprintf(stderr, "couldn't " "enable PEC\n"); else pmdev->use_pec = 1; } } } /* PMBus 1.0 has PMBUS_REVISION too; it's not new to PMBus 1.1 */ if (checksupport(pmdev, PMB_PMBUS_REVISION) != 0) { status = pmbus_read_byte_data(pmdev->fd, PMB_PMBUS_REVISION); if (status < 0) { if (verbose) fprintf(stderr, "No PMBUS_REVISION support; " "assuming 1.0\n"); } else pmdev->revision = status; } /* * We may not find out anything about the device in the code above; * PMBUS 1.1 conformant devices only need to implement ONE command * that's not manufacturer-specific. * * REVISIT support part definition files, probably using XML as a * relatively neutral syntax that's easily extended and manipulated, * and which doesn't need Yet Another Lowlevel Parser. * * - They should be able to represent all the data we can query * from fully dynamic PMBus 1.1 devices. * * - And support lookup by basic product ID strings, for PMBus 1.0 * devices that may expose little more than those strings and * some sensor/control attributes. * * - Provide a "dump" option, to generate those files for devices * which do happen to be fully query-able. * * - Allow cross-validation (file against device, to find bugs), * and merging (file adds extra data, e.g. coefficients from * docs rather than by protocol requests). * * - File should be able to describe things like manufacturer * specific commands, format of user data, accuracy, how the * various controls and sensors work in the local environment, * what "pages" exist (and what each one does), and so on. * * Doing all that usefully depends on having a variety of PMBus * based products running Linux. At this writing there aren't * very many of them, although some recent Dell rackmount servers * are described as having PMBus support. */ return 0; } /*----------------------------------------------------------------------*/ static const unsigned long i2c_func_pmbus_min = I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_PROC_CALL; int main(int argc, char **argv) { int c; struct pmbus_dev dev; char *adapter = "/dev/i2c-0"; char *addr_tail; int addr; bool clear = false; bool force = false; bool list = false; bool show = false; u8 mfr_cmd = 0; while ((c = getopt(argc, argv, "b:Cflpsv" #ifdef HACK "m:" #endif )) != EOF) { switch (c) { case 'b': adapter = optarg; continue; case 'C': clear = true; continue; case 'f': force = true; continue; case 'l': list = true; continue; #ifdef HACK case 'm': c = atoi(optarg); if (c < 0 || c > 45) { fprintf(stderr, "mfr_specific_%s ??\n", optarg); goto usage; } mfr_cmd = PMB_MFR_SPECIFIC(c); continue; #endif case 'p': enable_pec = 1; continue; case 's': show = true; continue; case 'v': verbose++; continue; case '?': default: goto usage; } } if (optind == argc || *argv[optind] == '\0') { fprintf(stderr, "missing device address\n"); goto usage; } if (optind != (argc - 1)) { fprintf(stderr, "too many arguments\n"); goto usage; } addr = (int) strtol(argv[optind], &addr_tail, 0); if (*addr_tail || addr < 0) { fprintf(stderr, "'%s' is not a device address\n", optarg); goto usage; } /* SMBUS 2.0 table 4 lists reserved addresses */ if (addr < 0x09 || addr > 0x77 || addr == 0x0c || addr == 0x28 || addr == 0x37 || addr == 0x61) { fprintf(stderr, "%#02x' is a reserved device address\n", addr); goto usage; } /* * Set up a handle for the specified device on its bus. */ memset(&dev, 0, sizeof dev); dev.fd = open(adapter, O_RDWR); if (dev.fd < 0) { perror(adapter); fprintf(stderr, "Couldn't connect to I2C bus %s\n", adapter); return 1; } dev.bus = adapter; c = ioctl(dev.fd, I2C_FUNCS, &dev.funcs); if (c < 0) { perror(adapter); fprintf(stderr, "%s: Couldn't get funcs\n", adapter); return 1; } /* Trying for portability here. We want to support all core PMBus * features. Minimal SMBus support is almost good enough ... except * for block read/write and block proc calls. So we insist on I2C * where the SMBus support is weak, and if it's available we also use * it to cope with the annoying "refuse to do 33+ byte blocks" limit. * * NOTE: WRITE_BLOCK isn't currently used -- or required -- so the * pmbus_block_write() method might fail on some systems. If that * matters in your usage, add another test ... */ if ((dev.funcs & i2c_func_pmbus_min) != i2c_func_pmbus_min || !(dev.funcs & (I2C_FUNC_SMBUS_READ_BLOCK_DATA | I2C_FUNC_I2C)) || !(dev.funcs & (I2C_FUNC_SMBUS_BLOCK_PROC_CALL | I2C_FUNC_I2C)) ) { fprintf(stderr, "%s: Funcs don't support PMBus\n", adapter); return 1; } /* some adapter drivers don't support PEC */ if (!(dev.funcs & I2C_FUNC_SMBUS_PEC) && enable_pec) { fprintf(stderr, "%s: No PEC support\n", adapter); enable_pec = 0; } c = ioctl(dev.fd, force ? I2C_SLAVE_FORCE : I2C_SLAVE, addr); if (c < 0) { perror(adapter); fprintf(stderr, "Couldn't %sattach to device %#02x\n", force ? "force " : "", addr); return 1; } dev.addr = addr; if (pmbus_dev_scan(&dev) < 0) return 1; if (show || list) pmbus_dev_show(&dev, show, list); if (clear) pmbus_clear_fault(&dev); #ifdef HACK if (mfr_cmd && checksupport(&dev, mfr_cmd) == 0) { printf("Unsuppported mfr_specific command: %#02x\n", mfr_cmd); mfr_cmd = 0; } if (mfr_cmd) { if (verbose) printf("Issuing mfr_specific command, %#02x...\n", mfr_cmd); /* NOTE: manufacturer commands can be of arbitrary syntax; * the hack here is that we "know" it's write-only, no-data. */ c = smbus_write_byte(dev.fd, mfr_cmd); if (c < 0) fprintf(stderr, "Error %d on mfr cmd %#02x\n", c, mfr_cmd); } #endif return 0; usage: fprintf(stderr, "Usage: %s [options] addr\n" " SMBus address may be in hex, decimal, or octal.\n" " Valid addresses include 0x09-0x77, with exceptions\n" "\n" "Options include:\n" " -b /dev/i2c-X specify I2C bus adapter for bus X\n" " (default bus is i2c-0)\n" " -C clear all status flags\n" " -f bypass 'address in use' checks\n" " (needed with new-style I2C systems)\n" " -l list device capabilities\n" #ifdef HACK " -m NN issue no-param mfr_specific_NN\n" #endif " -p enable PEC, if the device supports it\n" " -s show device status and attribute values\n" " -v be more verbose\n" , argv[0]); return 1; }