lkml.org 
[lkml]   [2010]   [Jul]   [5]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH/RFC v2 2/4] hwmon: i2c PMBus device emulator
    Date
    Signed-off-by: Guenter Roeck <guenter.roeck@ericsson.com>
    ---
    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@ericsson.com>");
    +MODULE_DESCRIPTION("I2C PMBus chip emulator");
    +MODULE_LICENSE("GPL");
    +
    +module_init(i2c_pmbus_init);
    +module_exit(i2c_pmbus_exit);
    --
    1.7.0.87.g0901d


    \
     
     \ /
      Last update: 2010-07-05 06:19    [W:3.432 / U:0.056 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site