[PATCH/RFC v2 2/4] hwmon: i2c PMBus device emulator

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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


[Index of Archives]     [Linux GPIO]     [Linux SPI]     [Linux Hardward Monitoring]     [LM Sensors]     [Linux USB Devel]     [Linux Media]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux