Signed-off-by: Guenter Roeck <guenter.roeck@xxxxxxxxxxxx> --- Documentation/i2c/i2c-pmbus | 41 ++ drivers/i2c/busses/Kconfig | 13 + drivers/i2c/busses/Makefile | 1 + drivers/i2c/busses/i2c-pmbus.c | 877 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 932 insertions(+), 0 deletions(-) create mode 100644 Documentation/i2c/i2c-pmbus create mode 100644 drivers/i2c/busses/i2c-pmbus.c diff --git a/Documentation/i2c/i2c-pmbus b/Documentation/i2c/i2c-pmbus new file mode 100644 index 0000000..4ba01fc --- /dev/null +++ b/Documentation/i2c/i2c-pmbus @@ -0,0 +1,41 @@ +MODULE: i2c-pmbus + +DESCRIPTION: + +This module is a fake I2C/SMBus driver to emulate various PMBus devices. +It implements five types of SMBus commands: write quick, (r/w) byte, +(r/w) byte data, (r/w) word data, and (r/w) I2C block data. + +The driver supports various PMBus devices at fixed addresses. The following +PMBus devices are supported. + +Device Address +BMR453 0x10 +LTC2978 0x20 +MAX16064 0x30 +MAX8688 0x40 +UCD9240 0x50 + +No hardware is needed nor associated with this module. It will accept write +quick commands to the specified addresses; it will respond to the other +commands (also to the specified addresses) by reading from or writing to +arrays in memory. + +Once loaded, the driver randomly changes sensor readings, up to lower and upper +fault limits. This may cause alarms or faults to be raised. This is expected +behavior. + +The typical use-case is like this: + 1. load this module + 3. load the target chip driver module + 4. observe its behavior using the sensors command + + +CAVEATS: + +Support for multiple pages (PMBus PAGE command) is limited. Pages can be +selected, but there is only one set of data, causing all paged registers to +return the same values. + +The emulator does not support self-modification of sensor readings for devices +which have to be programmed in direct mode. diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index bceafbf..5ab4abc 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -731,6 +731,19 @@ config I2C_PCA_ISA delays when I2C/SMBus chip drivers are loaded (e.g. at boot time). If unsure, say N. +config I2C_PMBUS + tristate "I2C/PMBus Chip Emulator" + depends on EXPERIMENTAL + default 'n' + help + This module emulates various PMBus devices. It may be useful to + developers of PMBus client drivers. + + If you do build this module, be sure to read the notes and warnings + in <file:Documentation/i2c/i2c-pmbus>. + + If you don't know what to do here, definitely say N. + config I2C_SIBYTE tristate "SiByte SMBus interface" depends on SIBYTE_SB1xxx_SOC diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 936880b..c11884e 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -70,6 +70,7 @@ obj-$(CONFIG_I2C_TINY_USB) += i2c-tiny-usb.o obj-$(CONFIG_I2C_ACORN) += i2c-acorn.o obj-$(CONFIG_I2C_ELEKTOR) += i2c-elektor.o obj-$(CONFIG_I2C_PCA_ISA) += i2c-pca-isa.o +obj-$(CONFIG_I2C_PMBUS) += i2c-pmbus.o obj-$(CONFIG_I2C_SIBYTE) += i2c-sibyte.o obj-$(CONFIG_I2C_STUB) += i2c-stub.o obj-$(CONFIG_SCx200_ACB) += scx200_acb.o diff --git a/drivers/i2c/busses/i2c-pmbus.c b/drivers/i2c/busses/i2c-pmbus.c new file mode 100644 index 0000000..9716e2f --- /dev/null +++ b/drivers/i2c/busses/i2c-pmbus.c @@ -0,0 +1,877 @@ +/* + * i2c-pmbus.c - I2C/SMBus chip emulator + * + * Copyright (C) 2010 Ericsson AB. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/kthread.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/i2c.h> +#include <linux/random.h> +#include "../../hwmon/pmbus.h" + +#define NUM_CHIPS 5 + +enum chips { bmr453, ltc2978, max16064, max8688, ucd9240 }; + +/* + * Register sizes per PMBus specification. + */ +static s8 pmbus_regsize[256] = { + 1, 1, 1, 0, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 16, -1, -1, -1, -1, -1, + 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -1, -1, -1, -1, -1, + 16, 2, 2, 2, -1, 2, 2, 2, 2, 2, 1, 2, 2, 1, 2, 2, + 2, 1, 2, 2, 2, 1, 2, 1, 2, 1, 2, 2, 1, -1, -1, 2, + 1, 2, 2, 2, 1, 2, 1, 2, 2, 2, 1, 2, 1, 2, 2, 2, + 2, 2, 2, 1, 2, 2, 2, -1, 2, 1, 2, 2, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, 1, 2, 1, 1, 1, 1, 1, 1, + 1, 1, 1, -1, -1, -1, -1, -1, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 1, 16, 16, 16, 16, 16, 16, -1, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 14, 14, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +}; + +/* + * PMBus register types. 1=rw, 0=ro or undefined + */ +static s8 pmbus_rw[256] = { + 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +/* + * exp 0xf8 -> 2^-1; mantissa 0x01 = 0.5V + * exp 0xf0 -> 2^-2; mantissa 0x01 = 0.25V + * exp 0xd0 -> 2^-6; mantissa 0x01 = 0.025V, 0x10 = 0.25V + */ +#define V1P5_LINEAR 0xd060 +#define V2P5_LINEAR 0xd0a0 +#define V2P75_LINEAR 0xd0b0 +#define V3P25_LINEAR 0xd0d0 +#define V3P5_LINEAR 0xd0e0 +#define V3P75_LINEAR 0xd0f0 +#define V11P5_LINEAR 0xf817 +#define V11P25_LINEAR 0xf02d +#define V12_LINEAR 0xf818 +#define V12P5_LINEAR 0xf032 +#define V12P75_LINEAR 0xf033 + +static u16 pmbus_linear_data[256] = { + [PMBUS_PAGE] = 0, + + [PMBUS_CAPABILITY] = PB_CAPABILITY_SMBALERT, + [PMBUS_VOUT_MODE] = (PB_VOUT_MODE_LINEAR | 0x13), + /* linear, -13 */ + + [PMBUS_VIN_OV_FAULT_LIMIT] = V12P75_LINEAR, + [PMBUS_VIN_OV_WARN_LIMIT] = V12P5_LINEAR, + [PMBUS_VIN_UV_WARN_LIMIT] = V11P5_LINEAR, + [PMBUS_VIN_UV_FAULT_LIMIT] = V11P25_LINEAR, + + [PMBUS_VOUT_OV_FAULT_LIMIT] = V3P5_LINEAR, + [PMBUS_VOUT_OV_WARN_LIMIT] = V3P25_LINEAR, + [PMBUS_VOUT_UV_WARN_LIMIT] = V2P75_LINEAR, + [PMBUS_VOUT_UV_FAULT_LIMIT] = V2P5_LINEAR, + + [PMBUS_IOUT_OC_FAULT_LIMIT] = 22, + [PMBUS_IOUT_OC_LV_FAULT_LIMIT] = 30, + [PMBUS_IOUT_OC_WARN_LIMIT] = 20, + [PMBUS_IOUT_UC_FAULT_LIMIT] = 0, + + [PMBUS_IIN_OC_FAULT_LIMIT] = 20, + [PMBUS_IIN_OC_WARN_LIMIT] = 19, + + [PMBUS_POUT_OP_FAULT_LIMIT] = 0x1000 | 50, /* 200 */ + [PMBUS_POUT_OP_WARN_LIMIT] = 0x1000 | 45, /* 180 */ + [PMBUS_PIN_OP_WARN_LIMIT] = 0x1000 | 60, /* 240 */ + + [PMBUS_OT_FAULT_LIMIT] = 100, + [PMBUS_OT_WARN_LIMIT] = 90, + [PMBUS_UT_WARN_LIMIT] = 10, + [PMBUS_UT_FAULT_LIMIT] = 0, + + [PMBUS_READ_VIN] = V12_LINEAR, + [PMBUS_READ_IIN] = 2, + [PMBUS_READ_VCAP] = V11P5_LINEAR, + [PMBUS_READ_VOUT] = 3, + [PMBUS_READ_IOUT] = 8, + [PMBUS_READ_TEMPERATURE_1] = 44, + [PMBUS_READ_TEMPERATURE_2] = 45, + [PMBUS_READ_TEMPERATURE_3] = 46, + [PMBUS_READ_FAN_SPEED_1] = 99, + [PMBUS_READ_FAN_SPEED_2] = 98, + [PMBUS_READ_FAN_SPEED_3] = 97, + [PMBUS_READ_FAN_SPEED_4] = 96, + [PMBUS_READ_DUTY_CYCLE] = 77, + [PMBUS_READ_FREQUENCY] = 1234, + [PMBUS_READ_POUT] = 100, + [PMBUS_READ_PIN] = 170, +}; + +/* + * Values calculated from max16064 manual + */ +#define V3UF_MAXIM 0x1200 +#define V3UW_MAXIM 0x1500 +#define V3_MAXIM 0x176e +#define V3OW_MAXIM 0x1a00 +#define V3OF_MAXIM 0x2000 + +#define I10_MAXIM 0x0906 +#define I20_MAXIM 0x120d +#define I25_MAXIM 0x1691 + +#define T0_MAXIM 0 +#define T20_MAXIM 0xff68 +#define T40_MAXIM 0xfecf +#define T80_MAXIM 0xfd9f +#define T90_MAXIM 0xfd58 + +static u16 pmbus_maxim_data[256] = { + [PMBUS_VOUT_OV_FAULT_LIMIT] = V3OF_MAXIM, + [PMBUS_VOUT_OV_WARN_LIMIT] = V3OW_MAXIM, + [PMBUS_VOUT_UV_WARN_LIMIT] = V3UW_MAXIM, + [PMBUS_VOUT_UV_FAULT_LIMIT] = V3UF_MAXIM, + + [PMBUS_IOUT_OC_FAULT_LIMIT] = I25_MAXIM, + [PMBUS_IOUT_OC_WARN_LIMIT] = I20_MAXIM, + + [PMBUS_OT_FAULT_LIMIT] = T90_MAXIM, + [PMBUS_OT_WARN_LIMIT] = T80_MAXIM, + [PMBUS_UT_WARN_LIMIT] = T20_MAXIM, + [PMBUS_UT_FAULT_LIMIT] = T0_MAXIM, + + [PMBUS_READ_VOUT] = V3_MAXIM, + [PMBUS_READ_IOUT] = I10_MAXIM, + [PMBUS_READ_TEMPERATURE_1] = T40_MAXIM, +}; + +struct pmbus_chip { + char name[16]; + u8 addr; + bool linear; + u8 pages; /* Number of pages (for PAGE register) */ + u8 pointer; + s8 regsize[256]; + u16 *initdata; + u8 data[256][I2C_SMBUS_BLOCK_MAX]; +}; + +static struct pmbus_chip pmbus_chips[NUM_CHIPS] = { + [bmr453] = { + .name = "bmr453", + .addr = 0x10, + .linear = 1, + .regsize[PMBUS_PAGE] = -1, + .regsize[PMBUS_PHASE] = -1, + .regsize[PMBUS_CAPABILITY] = -1, + .regsize[PMBUS_QUERY] = -1, + .regsize[PMBUS_VOUT_MODE] = -1, + .regsize[PMBUS_COEFFICIENTS] = -1, + .regsize[PMBUS_POUT_MAX] = -1, + .regsize[PMBUS_READ_IIN] = -1, + .regsize[PMBUS_READ_VCAP] = -1, + .regsize[PMBUS_READ_TEMPERATURE_3] = -1, + .regsize[PMBUS_READ_FAN_SPEED_1] = -1, + .regsize[PMBUS_READ_FAN_SPEED_2] = -1, + .regsize[PMBUS_READ_FAN_SPEED_3] = -1, + .regsize[PMBUS_READ_FAN_SPEED_4] = -1, + .regsize[PMBUS_READ_POUT] = -1, + .regsize[PMBUS_READ_PIN] = -1, + .regsize[PMBUS_MFR_ID] = 11, + .regsize[PMBUS_MFR_MODEL] = 13, + .initdata = pmbus_linear_data, + .data[PMBUS_MFR_ID] = "Ericsson AB", + .data[PMBUS_MFR_MODEL] = "BMR453xxxx001", + }, + [ltc2978] = { + .name = "ltc2978", + .addr = 0x20, + .linear = 1, + .pages = 8, + .regsize[PMBUS_PHASE] = -1, + .regsize[PMBUS_CAPABILITY] = -1, + .regsize[PMBUS_QUERY] = -1, + .regsize[PMBUS_COEFFICIENTS] = -1, + .regsize[PMBUS_POUT_MAX] = -1, + .regsize[PMBUS_STATUS_IOUT] = -1, + .regsize[PMBUS_STATUS_OTHER] = -1, + .regsize[PMBUS_READ_IIN] = -1, + .regsize[PMBUS_READ_IOUT] = -1, + .regsize[PMBUS_READ_VCAP] = -1, + .regsize[PMBUS_READ_TEMPERATURE_2] = -1, + .regsize[PMBUS_READ_TEMPERATURE_3] = -1, + .regsize[PMBUS_READ_FAN_SPEED_1] = -1, + .regsize[PMBUS_READ_FAN_SPEED_2] = -1, + .regsize[PMBUS_READ_FAN_SPEED_3] = -1, + .regsize[PMBUS_READ_FAN_SPEED_4] = -1, + .regsize[PMBUS_READ_POUT] = -1, + .regsize[PMBUS_READ_PIN] = -1, + .regsize[PMBUS_MFR_ID] = -1, + .regsize[LTC2978_MFR_SPECIAL_ID] = 2, + .initdata = pmbus_linear_data, + .data[LTC2978_MFR_SPECIAL_ID] = { 0x01, 0x21 }, + }, + [max16064] = { + .name = "max16064", + .addr = 0x30, + .pages = 4, + .regsize[PMBUS_PHASE] = -1, + .regsize[PMBUS_QUERY] = -1, + .regsize[PMBUS_VOUT_MODE] = -1, + .regsize[PMBUS_COEFFICIENTS] = -1, + .regsize[PMBUS_POUT_MAX] = -1, + .regsize[PMBUS_STATUS_IOUT] = -1, + .regsize[PMBUS_STATUS_INPUT] = -1, + .regsize[PMBUS_STATUS_OTHER] = -1, + .regsize[PMBUS_READ_VIN] = -1, + .regsize[PMBUS_READ_IIN] = -1, + .regsize[PMBUS_READ_IOUT] = -1, + .regsize[PMBUS_READ_VCAP] = -1, + .regsize[PMBUS_READ_TEMPERATURE_2] = -1, + .regsize[PMBUS_READ_TEMPERATURE_3] = -1, + .regsize[PMBUS_READ_FAN_SPEED_1] = -1, + .regsize[PMBUS_READ_FAN_SPEED_2] = -1, + .regsize[PMBUS_READ_FAN_SPEED_3] = -1, + .regsize[PMBUS_READ_FAN_SPEED_4] = -1, + .regsize[PMBUS_READ_POUT] = -1, + .regsize[PMBUS_READ_PIN] = -1, + .regsize[PMBUS_MFR_ID] = 1, + .regsize[PMBUS_MFR_MODEL] = 1, + .initdata = pmbus_maxim_data, + .data[PMBUS_MFR_ID] = { 0x4d }, + .data[PMBUS_MFR_MODEL] = { 0x43 }, + }, + [max8688] = { + .name = "max8688", + .addr = 0x40, + .regsize[PMBUS_PAGE] = -1, + .regsize[PMBUS_PHASE] = -1, + .regsize[PMBUS_QUERY] = -1, + .regsize[PMBUS_CAPABILITY] = -1, + .regsize[PMBUS_VOUT_MODE] = -1, + .regsize[PMBUS_COEFFICIENTS] = -1, + .regsize[PMBUS_POUT_MAX] = -1, + .regsize[PMBUS_STATUS_IOUT] = -1, + .regsize[PMBUS_STATUS_VOUT] = -1, + .regsize[PMBUS_STATUS_TEMPERATURE] = -1, + .regsize[PMBUS_STATUS_CML] = -1, + .regsize[PMBUS_STATUS_INPUT] = -1, + .regsize[PMBUS_STATUS_OTHER] = -1, + .regsize[PMBUS_READ_VIN] = -1, + .regsize[PMBUS_READ_IIN] = -1, + .regsize[PMBUS_READ_VCAP] = -1, + .regsize[PMBUS_READ_TEMPERATURE_2] = -1, + .regsize[PMBUS_READ_TEMPERATURE_3] = -1, + .regsize[PMBUS_READ_FAN_SPEED_1] = -1, + .regsize[PMBUS_READ_FAN_SPEED_2] = -1, + .regsize[PMBUS_READ_FAN_SPEED_3] = -1, + .regsize[PMBUS_READ_FAN_SPEED_4] = -1, + .regsize[PMBUS_READ_POUT] = -1, + .regsize[PMBUS_READ_PIN] = -1, + .regsize[PMBUS_MFR_ID] = 2, + .regsize[PMBUS_MFR_MODEL] = 2, + .initdata = pmbus_maxim_data, + .data[PMBUS_MFR_ID] = { 0x4d, 0x01 }, + .data[PMBUS_MFR_MODEL] = { 0x41, 0x01 }, + }, + [ucd9240] = { + .name = "ucd9240", + .addr = 0x50, + .linear = 1, + .pages = 4, + .regsize[PMBUS_QUERY] = -1, + .regsize[PMBUS_COEFFICIENTS] = -1, + .regsize[PMBUS_POUT_MAX] = -1, + .regsize[PMBUS_UT_WARN_LIMIT] = -1, + .regsize[PMBUS_UT_FAULT_LIMIT] = -1, + .regsize[PMBUS_UT_FAULT_RESPONSE] = -1, + .regsize[PMBUS_IIN_OC_FAULT_LIMIT] = -1, + .regsize[PMBUS_IIN_OC_WARN_LIMIT] = -1, + .regsize[PMBUS_READ_VCAP] = -1, + .regsize[PMBUS_READ_TEMPERATURE_3] = -1, + .regsize[PMBUS_READ_FAN_SPEED_2] = -1, + .regsize[PMBUS_READ_FAN_SPEED_3] = -1, + .regsize[PMBUS_READ_FAN_SPEED_4] = -1, + .initdata = pmbus_linear_data, + }, +}; + +static void pmbus_set_error(struct pmbus_chip *chip, u8 sreg, u16 s1, u8 s2) +{ + if (pmbus_regsize[PMBUS_STATUS_BYTE] > 0 + && (!chip->regsize[PMBUS_STATUS_BYTE] + || chip->regsize[PMBUS_STATUS_BYTE] > 0)) + chip->data[PMBUS_STATUS_BYTE][0] |= s1 & 0xff; + if (pmbus_regsize[PMBUS_STATUS_WORD] > 0 + && (!chip->regsize[PMBUS_STATUS_WORD] + || chip->regsize[PMBUS_STATUS_WORD] > 0)) { + chip->data[PMBUS_STATUS_WORD][0] |= s1 & 0xff; + chip->data[PMBUS_STATUS_WORD][1] |= (s1 >> 8) & 0xff; + } + if (pmbus_regsize[sreg] > 0 + && (!chip->regsize[sreg] || chip->regsize[sreg] > 0)) + chip->data[sreg][0] |= s2; +} + +/* Return negative errno on error. */ +static s32 pmbus_xfer(struct i2c_adapter *adap, u16 addr, unsigned short flags, + char read_write, u8 command, int size, + union i2c_smbus_data *data) +{ + s32 ret; + int i, len, regsize; + struct pmbus_chip *chip = NULL; + + /* Search for the right chip */ + for (i = 0; i < NUM_CHIPS && pmbus_chips[i].addr; i++) { + if (addr == pmbus_chips[i].addr) { + chip = pmbus_chips + i; + break; + } + } + if (!chip) { + dev_dbg(&adap->dev, "No chip at address 0x%02x\n", addr); + return -ENODEV; + } + + if (size != I2C_SMBUS_QUICK && (pmbus_regsize[command] == -1 + || chip->regsize[command] == -1)) { + dev_dbg(&adap->dev, "Unsupported command 0x%02x\n", command); + return -EINVAL; + } + + if (size != I2C_SMBUS_QUICK && read_write == I2C_SMBUS_WRITE + && !pmbus_rw[command]) { + dev_dbg(&adap->dev, "Command 0x%02x is r/o ", command); + return -EACCES; + } + + switch (size) { + + case I2C_SMBUS_QUICK: + dev_dbg(&adap->dev, "smbus quick - addr 0x%02x\n", addr); + ret = 0; + break; + + case I2C_SMBUS_BYTE: + if (read_write == I2C_SMBUS_WRITE) { + if (command == PMBUS_CLEAR_FAULTS) { + chip->data[PMBUS_STATUS_BYTE][0] = 0; + chip->data[PMBUS_STATUS_WORD][0] = 0; + chip->data[PMBUS_STATUS_WORD][1] = 0; + chip->data[PMBUS_STATUS_CML][0] = 0; + chip->data[PMBUS_STATUS_VOUT][0] = 0; + chip->data[PMBUS_STATUS_IOUT][0] = 0; + chip->data[PMBUS_STATUS_INPUT][0] = 0; + chip->data[PMBUS_STATUS_TEMPERATURE][0] = 0; + chip->data[PMBUS_STATUS_OTHER][0] = 0; + chip->data[PMBUS_STATUS_FANS_1_2][0] = 0; + chip->data[PMBUS_STATUS_FANS_3_4][0] = 0; + } + chip->pointer = command; + dev_dbg(&adap->dev, "smbus byte - addr 0x%02x, " + "wrote 0x%02x.\n", addr, command); + } else { + data->byte = chip->data[chip->pointer][0]; + dev_dbg(&adap->dev, "smbus byte - addr 0x%02x, " + "read 0x%02x.\n", addr, data->byte); + } + + ret = 0; + break; + + case I2C_SMBUS_BYTE_DATA: + if (pmbus_regsize[command] < 1) { + pmbus_set_error(chip, PMBUS_STATUS_CML, PB_STATUS_CML, + PB_CML_FAULT_OTHER_COMM); + ret = -EINVAL; + break; + } + if (read_write == I2C_SMBUS_WRITE) { + if (command == PMBUS_PAGE + && data->byte > chip->pages) { + pmbus_set_error(chip, PMBUS_STATUS_CML, + PB_STATUS_CML, + PB_CML_FAULT_OTHER_COMM); + ret = -EINVAL; + break; + } + chip->data[command][0] = data->byte; + chip->data[command][1] = 0; + dev_dbg(&adap->dev, "smbus byte data - addr 0x%02x, " + "wrote 0x%02x at 0x%02x.\n", + addr, data->byte, command); + } else { + data->byte = chip->data[command][0]; + dev_dbg(&adap->dev, "smbus byte data - addr 0x%02x, " + "read 0x%02x at 0x%02x.\n", + addr, data->byte, command); + } + + ret = 0; + break; + + case I2C_SMBUS_WORD_DATA: + if (pmbus_regsize[command] < 2) { + pmbus_set_error(chip, PMBUS_STATUS_CML, PB_STATUS_CML, + PB_CML_FAULT_OTHER_COMM); + ret = -EINVAL; + break; + } + if (read_write == I2C_SMBUS_WRITE) { + chip->data[command][0] = data->word & 0xff; + chip->data[command][1] = (data->word >> 8) & 0xff; + dev_dbg(&adap->dev, "smbus word data - addr 0x%02x, " + "wrote 0x%04x at 0x%02x.\n", + addr, data->word, command); + } else { + data->word = (chip->data[command][0] + | (chip->data[command][1] << 8)); + dev_dbg(&adap->dev, "smbus word data - addr 0x%02x, " + "read 0x%04x at 0x%02x.\n", + addr, data->word, command); + } + + ret = 0; + break; + + case I2C_SMBUS_BLOCK_DATA: + len = data->block[0]; + regsize = pmbus_regsize[command]; + if (chip->regsize[command]) + regsize = chip->regsize[command]; + if (len <= 0 || len > regsize) { + pmbus_set_error(chip, PMBUS_STATUS_CML, PB_STATUS_CML, + PB_CML_FAULT_OTHER_COMM); + ret = -EINVAL; + break; + } + if (read_write == I2C_SMBUS_WRITE) { + for (i = 1; i <= len; i++) + chip->data[command][i-1] = data->block[i]; + } else { + data->block[0] = regsize; + for (i = 0; i < regsize; i++) + data->block[i+1] = chip->data[command][i]; + } + + ret = 0; + break; + + default: + dev_dbg(&adap->dev, "Unsupported I2C/SMBus command\n"); + ret = -EOPNOTSUPP; + break; + } /* switch (size) */ + + return ret; +} + +static u32 pmbus_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | + I2C_SMBUS_BLOCK_DATA; +} + +static const struct i2c_algorithm smbus_algorithm = { + .functionality = pmbus_func, + .smbus_xfer = pmbus_xfer, +}; + +static struct i2c_adapter pmbus_adapter = { + .owner = THIS_MODULE, + .class = I2C_CLASS_HWMON | I2C_CLASS_SPD, + .algo = &smbus_algorithm, + .nr = 2, + .name = "SMBus pmbus driver", +}; + +struct boundaries { + u8 reg; + u8 warn_low; + u8 warn_high; + u8 fault_low; + u8 fault_high; + u8 status_reg; + u8 warn_status_low; + u8 warn_status_high; + u8 fault_status_low; + u8 fault_status_high; + u8 g_status_bit_low; + u8 g_status_bit_high; + int min, max; +}; + +static struct boundaries boundaries[] = { + { + .reg = PMBUS_READ_VIN, + .warn_low = PMBUS_VIN_UV_WARN_LIMIT, + .warn_high = PMBUS_VIN_OV_WARN_LIMIT, + .fault_low = PMBUS_VIN_UV_FAULT_LIMIT, + .fault_high = PMBUS_VIN_OV_FAULT_LIMIT, + .status_reg = PMBUS_STATUS_INPUT, + .warn_status_low = PB_VOLTAGE_UV_WARNING, + .warn_status_high = PB_VOLTAGE_OV_WARNING, + .fault_status_low = PB_VOLTAGE_UV_FAULT, + .fault_status_high = PB_VOLTAGE_OV_FAULT, + .g_status_bit_low = PB_STATUS_VIN_UV, + .min = 0, + .max = 50000, + }, + { + .reg = PMBUS_READ_VOUT, + .warn_low = PMBUS_VOUT_UV_WARN_LIMIT, + .warn_high = PMBUS_VOUT_OV_WARN_LIMIT, + .fault_low = PMBUS_VOUT_UV_FAULT_LIMIT, + .fault_high = PMBUS_VOUT_OV_FAULT_LIMIT, + .status_reg = PMBUS_STATUS_VOUT, + .warn_status_low = PB_VOLTAGE_UV_WARNING, + .warn_status_high = PB_VOLTAGE_OV_WARNING, + .fault_status_low = PB_VOLTAGE_UV_FAULT, + .fault_status_high = PB_VOLTAGE_OV_FAULT, + .g_status_bit_high = PB_STATUS_VOUT_OV, + .min = 0, + .max = 50000, + }, + { + .reg = PMBUS_READ_PIN, + .warn_high = PMBUS_PIN_OP_WARN_LIMIT, + .status_reg = PMBUS_STATUS_INPUT, + .warn_status_high = PB_PIN_OP_WARNING, + .min = 0, + .max = 1000000, + }, + { + .reg = PMBUS_READ_POUT, + .warn_high = PMBUS_POUT_OP_WARN_LIMIT, + .fault_high = PMBUS_POUT_OP_FAULT_LIMIT, + .status_reg = PMBUS_STATUS_IOUT, + .warn_status_high = PB_POUT_OP_WARNING, + .fault_status_high = PB_POUT_OP_FAULT, + .min = 0, + .max = 1000000, + }, + { + .reg = PMBUS_READ_IIN, + .warn_high = PMBUS_IIN_OC_WARN_LIMIT, + .fault_high = PMBUS_IIN_OC_FAULT_LIMIT, + .status_reg = PMBUS_STATUS_INPUT, + .warn_status_high = PB_IIN_OC_WARNING, + .fault_status_high = PB_IIN_OC_FAULT, + .min = 0, + .max = 10000, + }, + { + .reg = PMBUS_READ_IOUT, + .warn_high = PMBUS_IOUT_OC_WARN_LIMIT, + .fault_low = PMBUS_IOUT_UC_FAULT_LIMIT, + .fault_high = PMBUS_IOUT_OC_FAULT_LIMIT, + .status_reg = PMBUS_STATUS_IOUT, + .warn_status_high = PB_IOUT_OC_WARNING, + .fault_status_low = PB_IOUT_UC_FAULT, + .fault_status_high = PB_IOUT_OC_FAULT, + .g_status_bit_high = PB_STATUS_IOUT_OC, + .min = 0, + .max = 10000, + }, + { + .reg = PMBUS_READ_TEMPERATURE_1, + .warn_low = PMBUS_UT_WARN_LIMIT, + .warn_high = PMBUS_OT_WARN_LIMIT, + .fault_low = PMBUS_UT_FAULT_LIMIT, + .fault_high = PMBUS_OT_FAULT_LIMIT, + .status_reg = PMBUS_STATUS_TEMPERATURE, + .warn_status_low = PB_TEMP_UT_WARNING, + .warn_status_high = PB_TEMP_OT_WARNING, + .fault_status_low = PB_TEMP_UT_FAULT, + .fault_status_high = PB_TEMP_OT_FAULT, + .g_status_bit_low = PB_STATUS_TEMPERATURE, + .g_status_bit_high = PB_STATUS_TEMPERATURE, + .min = 0, + .max = 100, + }, + { + .reg = PMBUS_READ_TEMPERATURE_2, + .warn_low = PMBUS_UT_WARN_LIMIT, + .warn_high = PMBUS_OT_WARN_LIMIT, + .fault_low = PMBUS_UT_FAULT_LIMIT, + .fault_high = PMBUS_OT_FAULT_LIMIT, + .status_reg = PMBUS_STATUS_TEMPERATURE, + .warn_status_low = PB_TEMP_UT_WARNING, + .warn_status_high = PB_TEMP_OT_WARNING, + .fault_status_low = PB_TEMP_UT_FAULT, + .fault_status_high = PB_TEMP_OT_FAULT, + .g_status_bit_low = PB_STATUS_TEMPERATURE, + .g_status_bit_high = PB_STATUS_TEMPERATURE, + .min = 0, + .max = 100, + }, + { + .reg = PMBUS_READ_TEMPERATURE_3, + .warn_low = PMBUS_UT_WARN_LIMIT, + .warn_high = PMBUS_OT_WARN_LIMIT, + .fault_low = PMBUS_UT_FAULT_LIMIT, + .fault_high = PMBUS_OT_FAULT_LIMIT, + .status_reg = PMBUS_STATUS_TEMPERATURE, + .warn_status_low = PB_TEMP_UT_WARNING, + .warn_status_high = PB_TEMP_OT_WARNING, + .fault_status_low = PB_TEMP_UT_FAULT, + .fault_status_high = PB_TEMP_OT_FAULT, + .g_status_bit_low = PB_STATUS_TEMPERATURE, + .g_status_bit_high = PB_STATUS_TEMPERATURE, + .min = 0, + .max = 100, + }, +}; + +static int lintoval(u16 adc) +{ + s16 exponent, mantissa; + int val; + + exponent = adc >> 11; + mantissa = adc & 0x07ff; + + if (exponent > 0x0f) + exponent |= 0xffe0; /* sign extend exponent */ + if (mantissa > 0x03ff) + mantissa |= 0xf800; /* sign extend mantissa */ + + /* scale result to milli-units */ + val = mantissa * 1000; + + if (exponent > 0) + val <<= exponent; + else if (exponent < 0) + val >>= -exponent; + + return val; +} + +static u16 valtolin(int val) +{ + s16 exponent = 0, mantissa = 0; + + if (val < 0) { + while (val < -1024 * 512) { + exponent++; + val /= 2; + } + while (val > -1024 * 256) { + exponent--; + val *= 2; + } + } else if (val > 0) { + while (val > 1024 * 512) { + exponent++; + val /= 2; + } + while (val < 1024 * 256) { + exponent--; + val *= 2; + } + } + mantissa = (val + 500) / 1000; + + return (mantissa & 0x7ff) | ((exponent << 11) & 0xf800); +} + +static void i2c_pmbus_update_chip(struct pmbus_chip *chip) +{ + int i, reg; + + for (reg = PMBUS_READ_VIN; reg <= PMBUS_READ_PIN; reg++) { + u16 regval; + s8 offset; + int val, lf = 0, lw = 0, uf = 0, uw = 0, factor; + struct boundaries *b; + + if (pmbus_regsize[reg] == -1 || chip->regsize[reg] == -1) + continue; + for (i = 0; i < ARRAY_SIZE(boundaries); i++) { + if (boundaries[i].reg == reg) + break; + } + if (i >= ARRAY_SIZE(boundaries)) + continue; + b = &boundaries[i]; + + /* Randomly increase or decrease value up to + * critical limit. If a limit is exceeded, set + * warning/fault flag as appropriate + */ + regval = (chip->data[reg][0] | (chip->data[reg][1] << 8)); + val = lintoval(regval); + get_random_bytes(&offset, 1); + /* Change value up to approximately 1% */ + factor = val >> 14; + if (factor < 0) + factor = -factor; + if (factor == 0) + factor = 1; + val += offset * factor; + if (b->fault_low) { + lf = lintoval(chip->data[b->fault_low][0] + | (chip->data[b->fault_low][1] << 8)); + if (val < lf) + val = lf - 1; + } + if (b->warn_low) { + lw = lintoval(chip->data[b->warn_low][0] + | (chip->data[b->warn_low][1] << 8)); + if (!b->fault_low && val < lw) + val = lw - 1; + } + if (!b->fault_low && !b->warn_low && val < b->min) + val = b->min; + if (b->fault_high) { + uf = lintoval(chip->data[b->fault_high][0] + | (chip->data[b->fault_high][1] << 8)); + if (val > uf) + val = uf + 1; + } + if (b->warn_high) { + uw = lintoval(chip->data[b->warn_high][0] + | (chip->data[b->warn_high][1] << 8)); + if (!b->fault_high && val > uw) + val = uw + 1; + } + if (!b->fault_high && !b->warn_high && val > b->max) + val = b->max; + if (b->status_reg && b->fault_status_low && b->fault_low) { + if (val < lf) { + chip->data[b->status_reg][0] + |= b->fault_status_low; + chip->data[PMBUS_STATUS_BYTE][0] |= + b->g_status_bit_low; + } + } + if (b->status_reg && b->fault_status_high && b->fault_high) { + if (val > uf) { + chip->data[b->status_reg][0] |= + b->fault_status_high; + chip->data[PMBUS_STATUS_BYTE][0] |= + b->g_status_bit_high; + } + } + if (b->status_reg && b->warn_status_low && b->warn_low) { + if (val < lw) { + chip->data[b->status_reg][0] |= + b->warn_status_low; + chip->data[PMBUS_STATUS_BYTE][0] |= + b->g_status_bit_low; + } + } + if (b->status_reg && b->warn_status_high && b->warn_high) { + if (val > uw) { + chip->data[b->status_reg][0] |= + b->warn_status_high; + chip->data[PMBUS_STATUS_BYTE][0] |= + b->g_status_bit_high; + } + } + regval = valtolin(val); + chip->data[reg][0] = regval & 0xff; + chip->data[reg][1] = (regval >> 8) & 0xff; + } +} + +static int i2c_pmbus_update_thread(void *p) +{ + while (!kthread_should_stop()) { + int i; + + for (i = 0; i < NUM_CHIPS; i++) { + struct pmbus_chip *chip = &pmbus_chips[i]; + + if (chip->addr && chip->linear) + i2c_pmbus_update_chip(chip); + } + if (kthread_should_stop()) + break; + msleep_interruptible(1000); + } + return 0; +} + +static struct task_struct *i2c_pmbus_kthread; + +static int __init i2c_pmbus_init(void) +{ + int i, j, ret; + + for (i = 0; i < NUM_CHIPS; i++) { + if (pmbus_chips[i].addr) { + u16 *initdata = pmbus_chips[i].initdata; + if (initdata) + for (j = 0; j < 256; j++) + if (initdata[j]) { + pmbus_chips[i].data[j][0] + = initdata[j] & 0xff; + pmbus_chips[i].data[j][1] + = (initdata[j] >> 8) & 0xff; + } + printk(KERN_INFO "i2c-pmbus: Virtual %s at 0x%02x\n", + pmbus_chips[i].name, pmbus_chips[i].addr); + } + } + + i2c_pmbus_kthread = kthread_run(i2c_pmbus_update_thread, NULL, + "pmbus_update"); + + ret = i2c_add_numbered_adapter(&pmbus_adapter); + return ret; +} + +static void __exit i2c_pmbus_exit(void) +{ + if (i2c_pmbus_kthread) + kthread_stop(i2c_pmbus_kthread); + i2c_del_adapter(&pmbus_adapter); +} + +MODULE_AUTHOR("Guenter Roeck <guenter.roeck@xxxxxxxxxxxx>"); +MODULE_DESCRIPTION("I2C PMBus chip emulator"); +MODULE_LICENSE("GPL"); + +module_init(i2c_pmbus_init); +module_exit(i2c_pmbus_exit); -- 1.7.0.87.g0901d -- To unsubscribe from this list: send the line "unsubscribe linux-i2c" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html