2010-06-25 14:42:11

by Guenter Roeck

[permalink] [raw]
Subject: [PATCH v4 0/3] hwmon: Driver for SMM665 Six-Channel Active DC Output Controller/Monitor

This patchset adds support for the monitoring features of the Summit
Microelectronics SMM665 Six-Channel Active DC Output Controller/Monitor.

------

v2 changes:
- Added (untested) support for SMM465, SMM764, and SMM766.
- Added vref module parameter to support non-default values of VREF_ADC.
- Added an explanation describing what the chip does with _min and _max limit
values.
- Removed alarm sysfs files (since the chip does not report alarm status in a
register, alarm detection was implemented by comparing limits with ADC readings,
which can as well be done in userland).
- Replaced register enums with defines.
- Made limit variable names better readable.
- Removed retries from smm665_read_adc().
- Check error reply from i2c_smbus_read_byte_data() and only accept ENXIO
when expecting NACK.
- Explained reason for byte swap after reading ADC registers in some more detail.
- Added debug messages to display the error reason when reading ADC registers
failed.

v3 changes:
- Return error codes from smm665_read_adc().
- Abort after first error in smm665_update_device() and return error code.
- Return ADC read errors to user.
- Added inX_lcrit and inX_crit attributes for min/max critical voltages.
- Added tempX_lcrit attribute for critical low temperature.
- Updated sysfs-interface to document support for new attributes.

v4 changes:
- Added missing unlock in error handling of smm665_update_device().
- Fixed minor style issue in MAINTAINERS
- Removed update to sysfs-interface from patchset since it has already
been applied.
- Added lcrit and crit attributes to driver documentation.


2010-06-25 14:41:46

by Guenter Roeck

[permalink] [raw]
Subject: [PATCH v2 2/3] hwmon: SMM665 driver documentation

Signed-off-by: Guenter Roeck <[email protected]>
---
Documentation/hwmon/smm665 | 151 ++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 151 insertions(+), 0 deletions(-)
create mode 100644 Documentation/hwmon/smm665

diff --git a/Documentation/hwmon/smm665 b/Documentation/hwmon/smm665
new file mode 100644
index 0000000..8c08f63
--- /dev/null
+++ b/Documentation/hwmon/smm665
@@ -0,0 +1,151 @@
+Kernel driver smm665
+====================
+
+Supported chips:
+ * Summit Microelectronics SMM465
+ Prefix: 'smm465'
+ Addresses scanned: -
+ Datasheet: Publicly available at the Summit Microelectronics website.
+ * Summit Microelectronics SMM665, SMM665B
+ Prefix: 'smm665'
+ Addresses scanned: -
+ Datasheet: Publicly available at the Summit Microelectronics website.
+ * Summit Microelectronics SMM665C
+ Prefix: 'smm665c'
+ Addresses scanned: -
+ Datasheet: Publicly available at the Summit Microelectronics website.
+ * Summit Microelectronics SMM764
+ Prefix: 'smm764'
+ Addresses scanned: -
+ Datasheet: Publicly available at the Summit Microelectronics website.
+ * Summit Microelectronics SMM766
+ Prefix: 'smm766'
+ Addresses scanned: -
+ Datasheet: Publicly available at the Summit Microelectronics website.
+
+Author: Guenter Roeck <[email protected]>
+
+
+Module Parameters
+-----------------
+
+* vref: int
+ Default: 1250 (mV)
+ Reference voltage on VREF_ADC pin in mV. It should not be necessary to set
+ this parameter unless a non-default reference voltage is used.
+
+
+Description
+-----------
+
+[From datasheet] The SMM665 is an Active DC Output power supply Controller
+that monitors, margins and cascade sequences power. The part monitors six
+power supply channels as well as VDD, 12V input, two general-purpose analog
+inputs and an internal temperature sensor using a 10-bit ADC.
+
+Each monitored channel has its own high and low limits, plus a critical
+limit.
+
+Support for SMM465, SMM764, and SMM766 has been implemented but is untested.
+
+
+Usage Notes
+-----------
+
+This driver does not probe for devices, since there is no register which
+can be safely used to identify the chip. You will have to instantiate
+the devices explicitly. When instantiating the device, you have to specify
+its configuration register address.
+
+Example: the following will load the driver for an SMM665 at address 0x57
+on I2C bus #1:
+$ modprobe smm665
+$ echo smm665 0x57 > /sys/bus/i2c/devices/i2c-1/new_device
+
+
+Sysfs entries
+-------------
+
+This driver uses the values in the datasheet to convert ADC register values
+into the values specified in the sysfs-interface document. All attributes are
+read only.
+
+Min, max, lcrit, and crit values are used by the chip to trigger external signals
+and/or other activity. Triggered signals can include HEALTHY, RST, Power Off,
+or Fault depending on the chip configuration. The driver reports values as lcrit
+or crit if exceeding the limits triggers RST, Power Off, or Fault, and as min or
+max otherwise. For details please see the SMM665 datasheet.
+
+For SMM465 and SMM764, values for Channel E and F are reported but undefined.
+
+in1_input 12V input voltage (mV)
+in2_input 3.3V (VDD) input voltage (mV)
+in3_input Channel A voltage (mV)
+in4_input Channel B voltage (mV)
+in5_input Channel C voltage (mV)
+in6_input Channel D voltage (mV)
+in7_input Channel E voltage (mV)
+in8_input Channel F voltage (mV)
+in9_input AIN1 voltage (mV)
+in10_input AIN2 voltage (mV)
+
+in1_min 12v input minimum voltage (mV)
+in2_min 3.3V (VDD) input minimum voltage (mV)
+in3_min Channel A minimum voltage (mV)
+in4_min Channel B minimum voltage (mV)
+in5_min Channel C minimum voltage (mV)
+in6_min Channel D minimum voltage (mV)
+in7_min Channel E minimum voltage (mV)
+in8_min Channel F minimum voltage (mV)
+in9_min AIN1 minimum voltage (mV)
+in10_min AIN2 minimum voltage (mV)
+
+in1_max 12v input maximum voltage (mV)
+in2_max 3.3V (VDD) input maximum voltage (mV)
+in3_max Channel A maximum voltage (mV)
+in4_max Channel B maximum voltage (mV)
+in5_max Channel C maximum voltage (mV)
+in6_max Channel D maximum voltage (mV)
+in7_max Channel E maximum voltage (mV)
+in8_max Channel F maximum voltage (mV)
+in9_max AIN1 maximum voltage (mV)
+in10_max AIN2 maximum voltage (mV)
+
+in1_lcrit 12v input critical minimum voltage (mV)
+in2_lcrit 3.3V (VDD) input critical minimum voltage (mV)
+in3_lcrit Channel A critical minimum voltage (mV)
+in4_lcrit Channel B critical minimum voltage (mV)
+in5_lcrit Channel C critical minimum voltage (mV)
+in6_lcrit Channel D critical minimum voltage (mV)
+in7_lcrit Channel E critical minimum voltage (mV)
+in8_lcrit Channel F critical minimum voltage (mV)
+in9_lcrit AIN1 critical minimum voltage (mV)
+in10_lcrit AIN2 critical minimum voltage (mV)
+
+in1_crit 12v input critical maximum voltage (mV)
+in2_crit 3.3V (VDD) input critical maximum voltage (mV)
+in3_crit Channel A critical maximum voltage (mV)
+in4_crit Channel B critical maximum voltage (mV)
+in5_crit Channel C critical maximum voltage (mV)
+in6_crit Channel D critical maximum voltage (mV)
+in7_crit Channel E critical maximum voltage (mV)
+in8_crit Channel F critical maximum voltage (mV)
+in9_crit AIN1 critical maximum voltage (mV)
+in10_crit AIN2 critical maximum voltage (mV)
+
+in1_fault 12v input fault
+in2_fault 3.3V (VDD) input fault
+in3_fault Channel A fault
+in4_fault Channel B fault
+in5_fault Channel C fault
+in6_fault Channel D fault
+in7_fault Channel E fault
+in8_fault Channel F fault
+in9_fault AIN1 fault
+in10_fault AIN2 fault
+
+temp1_input Chip tempererature
+temp1_min Mimimum chip tempererature
+temp1_max Maximum chip tempererature
+temp1_crit Critical chip tempererature
+temp1_fault Temperature fault
--
1.7.0.87.g0901d

2010-06-25 14:42:20

by Guenter Roeck

[permalink] [raw]
Subject: [PATCH v4 1/3] hwmon: Driver for SMM665 Six-Channel Active DC Output Controller/Monitor

This driver adds support for the monitoring features of the Summit
Microelectronics SMM665 Six-Channel Active DC Output Controller/Monitor.

Signed-off-by: Guenter Roeck <[email protected]>
---
drivers/hwmon/Kconfig | 15 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/smm665.c | 748 ++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 764 insertions(+), 0 deletions(-)
create mode 100644 drivers/hwmon/smm665.c

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index e19cf8e..c45c17e 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -739,6 +739,21 @@ config SENSORS_SIS5595
This driver can also be built as a module. If so, the module
will be called sis5595.

+config SENSORS_SMM665
+ tristate "Summit Microelectronics SMM665"
+ depends on I2C && EXPERIMENTAL
+ default n
+ help
+ If you say yes here you get support for the hardware monitoring
+ features of the Summit Microelectronics SMM665/SMM665B Six-Channel
+ Active DC Output Controller / Monitor.
+
+ Other supported chips are SMM465, SMM665C, SMM764, and SMM766.
+ Support for those chips is untested.
+
+ This driver can also be built as a module. If so, the module will
+ be called smm665.
+
config SENSORS_DME1737
tristate "SMSC DME1737, SCH311x and compatibles"
depends on I2C && EXPERIMENTAL
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 2138ceb..55b0940 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -86,6 +86,7 @@ obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o
obj-$(CONFIG_SENSORS_S3C) += s3c-hwmon.o
obj-$(CONFIG_SENSORS_SHT15) += sht15.o
obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o
+obj-$(CONFIG_SENSORS_SMM665) += smm665.o
obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o
obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o
obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o
diff --git a/drivers/hwmon/smm665.c b/drivers/hwmon/smm665.c
new file mode 100644
index 0000000..760afba
--- /dev/null
+++ b/drivers/hwmon/smm665.c
@@ -0,0 +1,748 @@
+/*
+ * Driver for SMM665 Power Controller / Monitor
+ *
+ * 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; version 2 of the License.
+ *
+ * This driver should also work for SMM465, SMM764, and SMM766, but is untested
+ * for those chips. Only monitoring functionality is implemented.
+ *
+ * Datasheets:
+ * http://www.summitmicro.com/prod_select/summary/SMM665/SMM665B_2089_20.pdf
+ * http://www.summitmicro.com/prod_select/summary/SMM766B/SMM766B_2122.pdf
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/delay.h>
+
+/* Internal reference voltage (VREF, x 1000 */
+#define SMM665_VREF_ADC_X1000 1250
+
+/* module parameters */
+static int vref = SMM665_VREF_ADC_X1000;
+module_param(vref, int, 0);
+MODULE_PARM_DESC(vref, "Reference voltage in mV");
+
+enum chips { smm465, smm665, smm665c, smm764, smm766 };
+
+/*
+ * ADC channel addresses
+ */
+#define SMM665_MISC16_ADC_DATA_A 0x00
+#define SMM665_MISC16_ADC_DATA_B 0x01
+#define SMM665_MISC16_ADC_DATA_C 0x02
+#define SMM665_MISC16_ADC_DATA_D 0x03
+#define SMM665_MISC16_ADC_DATA_E 0x04
+#define SMM665_MISC16_ADC_DATA_F 0x05
+#define SMM665_MISC16_ADC_DATA_VDD 0x06
+#define SMM665_MISC16_ADC_DATA_12V 0x07
+#define SMM665_MISC16_ADC_DATA_INT_TEMP 0x08
+#define SMM665_MISC16_ADC_DATA_AIN1 0x09
+#define SMM665_MISC16_ADC_DATA_AIN2 0x0a
+
+/*
+ * Command registers
+ */
+#define SMM665_MISC8_CMD_STS 0x80
+#define SMM665_MISC8_STATUS1 0x81
+#define SMM665_MISC8_STATUSS2 0x82
+#define SMM665_MISC8_IO_POLARITY 0x83
+#define SMM665_MISC8_PUP_POLARITY 0x84
+#define SMM665_MISC8_ADOC_STATUS1 0x85
+#define SMM665_MISC8_ADOC_STATUS2 0x86
+#define SMM665_MISC8_WRITE_PROT 0x87
+#define SMM665_MISC8_STS_TRACK 0x88
+
+/*
+ * Configuration registers and register groups
+ */
+#define SMM665_ADOC_ENABLE 0x0d
+#define SMM665_LIMIT_BASE 0x80 /* First limit register */
+
+/*
+ * Limit register bit masks
+ */
+#define SMM665_TRIGGER_RST 0x8000
+#define SMM665_TRIGGER_HEALTHY 0x4000
+#define SMM665_TRIGGER_POWEROFF 0x2000
+#define SMM665_TRIGGER_SHUTDOWN 0x1000
+#define SMM665_ADC_MASK 0x03ff
+
+#define smm665_is_critical(lim) ((lim) & (SMM665_TRIGGER_RST \
+ | SMM665_TRIGGER_POWEROFF \
+ | SMM665_TRIGGER_SHUTDOWN))
+/*
+ * Fault register bit definitions
+ * Values are merged from status registers 1/2,
+ * with status register 1 providing the upper 8 bits.
+ */
+#define SMM665_FAULT_A 0x0001
+#define SMM665_FAULT_B 0x0002
+#define SMM665_FAULT_C 0x0004
+#define SMM665_FAULT_D 0x0008
+#define SMM665_FAULT_E 0x0010
+#define SMM665_FAULT_F 0x0020
+#define SMM665_FAULT_VDD 0x0040
+#define SMM665_FAULT_12V 0x0080
+#define SMM665_FAULT_TEMP 0x0100
+#define SMM665_FAULT_AIN1 0x0200
+#define SMM665_FAULT_AIN2 0x0400
+
+/*
+ * I2C Register addresses
+ *
+ * The configuration register needs to be the configured base register.
+ * The command/status register address is derived from it.
+ */
+#define SMM665_REGMASK 0x78
+#define SMM665_CMDREG_BASE 0x48
+#define SMM665_CONFREG_BASE 0x50
+
+/*
+ * Equations given by chip manufacturer to calculate voltage/temperature values
+ * vref = Reference voltage on VREF_ADC pin (module parameter)
+ * adc = 10bit ADC value read back from registers
+ */
+
+/* Voltage A-F and VDD */
+#define SMM665_VMON_ADC_TO_VOLTS(adc) ((adc) * vref / 256)
+
+/* Voltage 12VIN */
+#define SMM665_12VIN_ADC_TO_VOLTS(adc) ((adc) * vref * 3 / 256)
+
+/* Voltage AIN1, AIN2 */
+#define SMM665_AIN_ADC_TO_VOLTS(adc) ((adc) * vref / 512)
+
+/* Temp Sensor */
+#define SMM665_TEMP_ADC_TO_CELSIUS(adc) ((adc) <= 511) ? \
+ ((int)(adc) * 1000 / 4) : \
+ (((int)(adc) - 0x400) * 1000 / 4)
+
+#define SMM665_NUM_ADC 11
+
+/*
+ * Chip dependent ADC conversion time, in uS
+ */
+#define SMM665_ADC_WAIT_SMM665 70
+#define SMM665_ADC_WAIT_SMM766 185
+
+struct smm665_data {
+ enum chips type;
+ int conversion_time; /* ADC conversion time */
+ struct device *hwmon_dev;
+ struct mutex update_lock;
+ bool valid;
+ unsigned long last_updated; /* in jiffies */
+ u16 adc[SMM665_NUM_ADC]; /* adc values (raw) */
+ u16 faults; /* fault status */
+ /* The following values are in mV */
+ int critical_min_limit[SMM665_NUM_ADC];
+ int alarm_min_limit[SMM665_NUM_ADC];
+ int critical_max_limit[SMM665_NUM_ADC];
+ int alarm_max_limit[SMM665_NUM_ADC];
+ struct i2c_client *cmdreg;
+};
+
+/*
+ * smm665_read16()
+ *
+ * Read 16 bit value from <reg>, <reg+1>. Upper 8 bits are in <reg>.
+ */
+static int smm665_read16(struct i2c_client *client, int reg)
+{
+ int rv, val;
+
+ rv = i2c_smbus_read_byte_data(client, reg);
+ if (rv < 0)
+ return rv;
+ val = rv << 8;
+ rv = i2c_smbus_read_byte_data(client, reg + 1);
+ if (rv < 0)
+ return rv;
+ val |= rv;
+ return val;
+}
+
+/*
+ * Read adc value.
+ */
+static int smm665_read_adc(struct smm665_data *data, int adc)
+{
+ struct i2c_client *client = data->cmdreg;
+ int rv;
+ int radc;
+
+ /*
+ * Algorithm for reading ADC, per SMM665 datasheet
+ *
+ * {[S][addr][W][Ack]} {[offset][Ack]} {[S][addr][R][Nack]}
+ * [wait conversion time]
+ * {[S][addr][R][Ack]} {[datahi][Ack]} {[datalo][Ack][P]}
+ *
+ * To implement the first part of this exchange,
+ * do a full read transaction and expect a failure/Nack.
+ * This sets up the address pointer on the SMM665
+ * and starts the ADC conversion.
+ * Then do a two-byte read transaction.
+ */
+ rv = i2c_smbus_read_byte_data(client, adc << 3);
+ if (rv != -ENXIO) {
+ /*
+ * We expect ENXIO to reflect NACK
+ * (per Documentation/i2c/fault-codes).
+ * Everything else is an error.
+ */
+ dev_dbg(&client->dev,
+ "Unexpected return code %d when setting ADC index", rv);
+ return (rv < 0) ? rv : -EIO;
+ }
+
+ udelay(data->conversion_time);
+
+ /*
+ * Now read two bytes.
+ *
+ * Neither i2c_smbus_read_byte() nor
+ * i2c_smbus_read_block_data() worked here,
+ * so use i2c_smbus_read_word_data() instead.
+ * We could also try to use i2c_master_recv(),
+ * but that is not always supported.
+ */
+ rv = i2c_smbus_read_word_data(client, 0);
+ if (rv < 0) {
+ dev_dbg(&client->dev, "Failed to read ADC value: error %d", rv);
+ return -1;
+ }
+ /*
+ * Validate/verify readback adc channel (in bit 11..14).
+ * High byte is in lower 8 bit of rv, so only shift by 3.
+ */
+ radc = (rv >> 3) & 0x0f;
+ if (radc != adc) {
+ dev_dbg(&client->dev, "Unexpected RADC: Expected %d got %d",
+ adc, radc);
+ return -EIO;
+ }
+ /*
+ * Chip replies with H/L, while SMBus expects L/H.
+ * Thus, byte order is reversed, and we have to swap
+ * the result.
+ */
+ rv = swab16(rv) & SMM665_ADC_MASK;
+
+ return rv;
+}
+
+static struct smm665_data *smm665_update_device(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct smm665_data *data = i2c_get_clientdata(client);
+ struct smm665_data *ret = data;
+
+ mutex_lock(&data->update_lock);
+
+ if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
+ int i, val;
+
+ /*
+ * read status registers
+ */
+ val = smm665_read16(client, SMM665_MISC8_STATUS1);
+ if (unlikely(val < 0)) {
+ ret = ERR_PTR(val);
+ goto abort;
+ }
+ data->faults = val;
+
+ /* Read adc registers */
+ for (i = 0; i < SMM665_NUM_ADC; i++) {
+ val = smm665_read_adc(data, i);
+ if (unlikely(val < 0)) {
+ ret = ERR_PTR(val);
+ goto abort;
+ }
+ data->adc[i] = val;
+ }
+ data->last_updated = jiffies;
+ data->valid = 1;
+ }
+abort:
+ mutex_unlock(&data->update_lock);
+ return ret;
+}
+
+/* Return converted value from given adc */
+static int smm665_convert(u16 adcval, int index)
+{
+ int val = 0;
+
+ switch (index) {
+ case SMM665_MISC16_ADC_DATA_12V:
+ val = SMM665_12VIN_ADC_TO_VOLTS(adcval & SMM665_ADC_MASK);
+ break;
+
+ case SMM665_MISC16_ADC_DATA_VDD:
+ case SMM665_MISC16_ADC_DATA_A:
+ case SMM665_MISC16_ADC_DATA_B:
+ case SMM665_MISC16_ADC_DATA_C:
+ case SMM665_MISC16_ADC_DATA_D:
+ case SMM665_MISC16_ADC_DATA_E:
+ case SMM665_MISC16_ADC_DATA_F:
+ val = SMM665_VMON_ADC_TO_VOLTS(adcval & SMM665_ADC_MASK);
+ break;
+
+ case SMM665_MISC16_ADC_DATA_AIN1:
+ case SMM665_MISC16_ADC_DATA_AIN2:
+ val = SMM665_AIN_ADC_TO_VOLTS(adcval & SMM665_ADC_MASK);
+ break;
+
+ case SMM665_MISC16_ADC_DATA_INT_TEMP:
+ val = SMM665_TEMP_ADC_TO_CELSIUS(adcval & SMM665_ADC_MASK);
+ break;
+
+ default:
+ /* If we get here, the developer messed up */
+ WARN_ON_ONCE(1);
+ break;
+ }
+
+ return val;
+}
+
+static int smm665_get_min(struct device *dev, int index)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct smm665_data *data = i2c_get_clientdata(client);
+
+ return data->alarm_min_limit[index];
+}
+
+static int smm665_get_max(struct device *dev, int index)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct smm665_data *data = i2c_get_clientdata(client);
+
+ return data->alarm_max_limit[index];
+}
+
+static int smm665_get_lcrit(struct device *dev, int index)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct smm665_data *data = i2c_get_clientdata(client);
+
+ return data->critical_min_limit[index];
+}
+
+static int smm665_get_crit(struct device *dev, int index)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct smm665_data *data = i2c_get_clientdata(client);
+
+ return data->critical_max_limit[index];
+}
+
+static ssize_t smm665_show_fault(struct device *dev,
+ struct device_attribute *da, char *buf)
+{
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
+ struct smm665_data *data = smm665_update_device(dev);
+ int val = 0;
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ if (data->faults & (1 << attr->index))
+ val = 1;
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", val);
+}
+
+static ssize_t smm665_show_input(struct device *dev,
+ struct device_attribute *da, char *buf)
+{
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
+ struct smm665_data *data = smm665_update_device(dev);
+ int adc = attr->index;
+ int val;
+
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ val = smm665_convert(data->adc[adc], adc);
+ return snprintf(buf, PAGE_SIZE, "%d\n", val);
+}
+
+#define SMM665_SHOW(what) \
+ static ssize_t smm665_show_##what(struct device *dev, \
+ struct device_attribute *da, char *buf) \
+{ \
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(da); \
+ const int val = smm665_get_##what(dev, attr->index); \
+ return snprintf(buf, PAGE_SIZE, "%d\n", val); \
+}
+
+SMM665_SHOW(min);
+SMM665_SHOW(max);
+SMM665_SHOW(lcrit);
+SMM665_SHOW(crit);
+
+/* These macros are used below in constructing device attribute objects
+ * for use with sysfs_create_group() to make a sysfs device file
+ * for each register.
+ */
+
+#define SMM665_ATTR(name, type, cmd_idx) \
+ static SENSOR_DEVICE_ATTR(name##_##type, S_IRUGO, \
+ smm665_show_##type, NULL, cmd_idx)
+
+/* Construct a sensor_device_attribute structure for each register */
+
+/* Input voltages */
+SMM665_ATTR(in1, input, SMM665_MISC16_ADC_DATA_12V);
+SMM665_ATTR(in2, input, SMM665_MISC16_ADC_DATA_VDD);
+SMM665_ATTR(in3, input, SMM665_MISC16_ADC_DATA_A);
+SMM665_ATTR(in4, input, SMM665_MISC16_ADC_DATA_B);
+SMM665_ATTR(in5, input, SMM665_MISC16_ADC_DATA_C);
+SMM665_ATTR(in6, input, SMM665_MISC16_ADC_DATA_D);
+SMM665_ATTR(in7, input, SMM665_MISC16_ADC_DATA_E);
+SMM665_ATTR(in8, input, SMM665_MISC16_ADC_DATA_F);
+SMM665_ATTR(in9, input, SMM665_MISC16_ADC_DATA_AIN1);
+SMM665_ATTR(in10, input, SMM665_MISC16_ADC_DATA_AIN2);
+
+/* Input voltages min */
+SMM665_ATTR(in1, min, SMM665_MISC16_ADC_DATA_12V);
+SMM665_ATTR(in2, min, SMM665_MISC16_ADC_DATA_VDD);
+SMM665_ATTR(in3, min, SMM665_MISC16_ADC_DATA_A);
+SMM665_ATTR(in4, min, SMM665_MISC16_ADC_DATA_B);
+SMM665_ATTR(in5, min, SMM665_MISC16_ADC_DATA_C);
+SMM665_ATTR(in6, min, SMM665_MISC16_ADC_DATA_D);
+SMM665_ATTR(in7, min, SMM665_MISC16_ADC_DATA_E);
+SMM665_ATTR(in8, min, SMM665_MISC16_ADC_DATA_F);
+SMM665_ATTR(in9, min, SMM665_MISC16_ADC_DATA_AIN1);
+SMM665_ATTR(in10, min, SMM665_MISC16_ADC_DATA_AIN2);
+
+/* Input voltages max */
+SMM665_ATTR(in1, max, SMM665_MISC16_ADC_DATA_12V);
+SMM665_ATTR(in2, max, SMM665_MISC16_ADC_DATA_VDD);
+SMM665_ATTR(in3, max, SMM665_MISC16_ADC_DATA_A);
+SMM665_ATTR(in4, max, SMM665_MISC16_ADC_DATA_B);
+SMM665_ATTR(in5, max, SMM665_MISC16_ADC_DATA_C);
+SMM665_ATTR(in6, max, SMM665_MISC16_ADC_DATA_D);
+SMM665_ATTR(in7, max, SMM665_MISC16_ADC_DATA_E);
+SMM665_ATTR(in8, max, SMM665_MISC16_ADC_DATA_F);
+SMM665_ATTR(in9, max, SMM665_MISC16_ADC_DATA_AIN1);
+SMM665_ATTR(in10, max, SMM665_MISC16_ADC_DATA_AIN2);
+
+/* Input voltages lcrit */
+SMM665_ATTR(in1, lcrit, SMM665_MISC16_ADC_DATA_12V);
+SMM665_ATTR(in2, lcrit, SMM665_MISC16_ADC_DATA_VDD);
+SMM665_ATTR(in3, lcrit, SMM665_MISC16_ADC_DATA_A);
+SMM665_ATTR(in4, lcrit, SMM665_MISC16_ADC_DATA_B);
+SMM665_ATTR(in5, lcrit, SMM665_MISC16_ADC_DATA_C);
+SMM665_ATTR(in6, lcrit, SMM665_MISC16_ADC_DATA_D);
+SMM665_ATTR(in7, lcrit, SMM665_MISC16_ADC_DATA_E);
+SMM665_ATTR(in8, lcrit, SMM665_MISC16_ADC_DATA_F);
+SMM665_ATTR(in9, lcrit, SMM665_MISC16_ADC_DATA_AIN1);
+SMM665_ATTR(in10, lcrit, SMM665_MISC16_ADC_DATA_AIN2);
+
+/* Input voltages crit */
+SMM665_ATTR(in1, crit, SMM665_MISC16_ADC_DATA_12V);
+SMM665_ATTR(in2, crit, SMM665_MISC16_ADC_DATA_VDD);
+SMM665_ATTR(in3, crit, SMM665_MISC16_ADC_DATA_A);
+SMM665_ATTR(in4, crit, SMM665_MISC16_ADC_DATA_B);
+SMM665_ATTR(in5, crit, SMM665_MISC16_ADC_DATA_C);
+SMM665_ATTR(in6, crit, SMM665_MISC16_ADC_DATA_D);
+SMM665_ATTR(in7, crit, SMM665_MISC16_ADC_DATA_E);
+SMM665_ATTR(in8, crit, SMM665_MISC16_ADC_DATA_F);
+SMM665_ATTR(in9, crit, SMM665_MISC16_ADC_DATA_AIN1);
+SMM665_ATTR(in10, crit, SMM665_MISC16_ADC_DATA_AIN2);
+
+/* Faults */
+SMM665_ATTR(in1, fault, SMM665_FAULT_12V);
+SMM665_ATTR(in2, fault, SMM665_FAULT_VDD);
+SMM665_ATTR(in3, fault, SMM665_FAULT_A);
+SMM665_ATTR(in4, fault, SMM665_FAULT_B);
+SMM665_ATTR(in5, fault, SMM665_FAULT_C);
+SMM665_ATTR(in6, fault, SMM665_FAULT_D);
+SMM665_ATTR(in7, fault, SMM665_FAULT_E);
+SMM665_ATTR(in8, fault, SMM665_FAULT_F);
+SMM665_ATTR(in9, fault, SMM665_FAULT_AIN1);
+SMM665_ATTR(in10, fault, SMM665_FAULT_AIN2);
+
+/* Temperature */
+SMM665_ATTR(temp1, input, SMM665_MISC16_ADC_DATA_INT_TEMP);
+SMM665_ATTR(temp1, min, SMM665_MISC16_ADC_DATA_INT_TEMP);
+SMM665_ATTR(temp1, max, SMM665_MISC16_ADC_DATA_INT_TEMP);
+SMM665_ATTR(temp1, lcrit, SMM665_MISC16_ADC_DATA_INT_TEMP);
+SMM665_ATTR(temp1, crit, SMM665_MISC16_ADC_DATA_INT_TEMP);
+SMM665_ATTR(temp1, fault, SMM665_FAULT_TEMP);
+
+/*
+ * Finally, construct an array of pointers to members of the above objects,
+ * as required for sysfs_create_group()
+ */
+static struct attribute *smm665_attributes[] = {
+ &sensor_dev_attr_in1_input.dev_attr.attr,
+ &sensor_dev_attr_in1_min.dev_attr.attr,
+ &sensor_dev_attr_in1_max.dev_attr.attr,
+ &sensor_dev_attr_in1_lcrit.dev_attr.attr,
+ &sensor_dev_attr_in1_crit.dev_attr.attr,
+ &sensor_dev_attr_in1_fault.dev_attr.attr,
+
+ &sensor_dev_attr_in2_input.dev_attr.attr,
+ &sensor_dev_attr_in2_min.dev_attr.attr,
+ &sensor_dev_attr_in2_max.dev_attr.attr,
+ &sensor_dev_attr_in2_lcrit.dev_attr.attr,
+ &sensor_dev_attr_in2_crit.dev_attr.attr,
+ &sensor_dev_attr_in2_fault.dev_attr.attr,
+
+ &sensor_dev_attr_in3_input.dev_attr.attr,
+ &sensor_dev_attr_in3_min.dev_attr.attr,
+ &sensor_dev_attr_in3_max.dev_attr.attr,
+ &sensor_dev_attr_in3_lcrit.dev_attr.attr,
+ &sensor_dev_attr_in3_crit.dev_attr.attr,
+ &sensor_dev_attr_in3_fault.dev_attr.attr,
+
+ &sensor_dev_attr_in4_input.dev_attr.attr,
+ &sensor_dev_attr_in4_min.dev_attr.attr,
+ &sensor_dev_attr_in4_max.dev_attr.attr,
+ &sensor_dev_attr_in4_lcrit.dev_attr.attr,
+ &sensor_dev_attr_in4_crit.dev_attr.attr,
+ &sensor_dev_attr_in4_fault.dev_attr.attr,
+
+ &sensor_dev_attr_in5_input.dev_attr.attr,
+ &sensor_dev_attr_in5_min.dev_attr.attr,
+ &sensor_dev_attr_in5_max.dev_attr.attr,
+ &sensor_dev_attr_in5_lcrit.dev_attr.attr,
+ &sensor_dev_attr_in5_crit.dev_attr.attr,
+ &sensor_dev_attr_in5_fault.dev_attr.attr,
+
+ &sensor_dev_attr_in6_input.dev_attr.attr,
+ &sensor_dev_attr_in6_min.dev_attr.attr,
+ &sensor_dev_attr_in6_max.dev_attr.attr,
+ &sensor_dev_attr_in6_lcrit.dev_attr.attr,
+ &sensor_dev_attr_in6_crit.dev_attr.attr,
+ &sensor_dev_attr_in6_fault.dev_attr.attr,
+
+ &sensor_dev_attr_in7_input.dev_attr.attr,
+ &sensor_dev_attr_in7_min.dev_attr.attr,
+ &sensor_dev_attr_in7_max.dev_attr.attr,
+ &sensor_dev_attr_in7_lcrit.dev_attr.attr,
+ &sensor_dev_attr_in7_crit.dev_attr.attr,
+ &sensor_dev_attr_in7_fault.dev_attr.attr,
+
+ &sensor_dev_attr_in8_input.dev_attr.attr,
+ &sensor_dev_attr_in8_min.dev_attr.attr,
+ &sensor_dev_attr_in8_max.dev_attr.attr,
+ &sensor_dev_attr_in8_lcrit.dev_attr.attr,
+ &sensor_dev_attr_in8_crit.dev_attr.attr,
+ &sensor_dev_attr_in8_fault.dev_attr.attr,
+
+ &sensor_dev_attr_in9_input.dev_attr.attr,
+ &sensor_dev_attr_in9_min.dev_attr.attr,
+ &sensor_dev_attr_in9_max.dev_attr.attr,
+ &sensor_dev_attr_in9_lcrit.dev_attr.attr,
+ &sensor_dev_attr_in9_crit.dev_attr.attr,
+ &sensor_dev_attr_in9_fault.dev_attr.attr,
+
+ &sensor_dev_attr_in10_input.dev_attr.attr,
+ &sensor_dev_attr_in10_min.dev_attr.attr,
+ &sensor_dev_attr_in10_max.dev_attr.attr,
+ &sensor_dev_attr_in10_lcrit.dev_attr.attr,
+ &sensor_dev_attr_in10_crit.dev_attr.attr,
+ &sensor_dev_attr_in10_fault.dev_attr.attr,
+
+ &sensor_dev_attr_temp1_input.dev_attr.attr,
+ &sensor_dev_attr_temp1_min.dev_attr.attr,
+ &sensor_dev_attr_temp1_max.dev_attr.attr,
+ &sensor_dev_attr_temp1_lcrit.dev_attr.attr,
+ &sensor_dev_attr_temp1_crit.dev_attr.attr,
+ &sensor_dev_attr_temp1_fault.dev_attr.attr,
+
+ NULL,
+};
+
+static const struct attribute_group smm665_group = {
+ .attrs = smm665_attributes,
+};
+
+static int smm665_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct i2c_adapter *adapter = client->adapter;
+ struct smm665_data *data;
+ int i, ret;
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA
+ | I2C_FUNC_SMBUS_WORD_DATA))
+ return -ENODEV;
+
+ if (i2c_smbus_read_byte_data(client, SMM665_ADOC_ENABLE) < 0)
+ return -ENODEV;
+
+ ret = -ENOMEM;
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (!data)
+ goto out_return;
+
+ i2c_set_clientdata(client, data);
+ mutex_init(&data->update_lock);
+
+ data->type = id->driver_data;
+ data->cmdreg = i2c_new_dummy(adapter, (client->addr & ~SMM665_REGMASK)
+ | SMM665_CMDREG_BASE);
+ if (!data->cmdreg)
+ goto out_kfree;
+
+ switch (data->type) {
+ case smm465:
+ case smm665:
+ data->conversion_time = SMM665_ADC_WAIT_SMM665;
+ break;
+ case smm665c:
+ case smm764:
+ case smm766:
+ data->conversion_time = SMM665_ADC_WAIT_SMM766;
+ break;
+ }
+
+ ret = -ENODEV;
+ if (i2c_smbus_read_byte_data(data->cmdreg, SMM665_MISC8_CMD_STS) < 0)
+ goto out_unregister;
+
+ /*
+ * Read limits.
+ *
+ * Limit registers start with register SMM665_LIMIT_BASE.
+ * Each channel uses 8 registers, providing four limit values
+ * per channel. Each limit value requires two registers, with the
+ * high byte in the first register and the low byte in the second
+ * register. The first two limits are under limit values, followed
+ * by two over limit values.
+ *
+ * Limit register order matches the ADC register order, so we use
+ * ADC register defines throughout the code to index limit registers.
+ *
+ * We save the first retrieved value both as "critical" and "alarm"
+ * value. The second value overwrites either the critical or the
+ * alarm value, depending on its configuration. This ensures that both
+ * critical and alarm values are initialized, even if both registers are
+ * configured as critical or non-critical.
+ *
+ * Note: Critical values for voltage channels are saved, even though
+ * this information is currently not used by the driver. This is mostly
+ * for consistency, though it might eventually be useful if future APIs
+ * support reporting "critical" voltage values.
+ */
+ for (i = 0; i < SMM665_NUM_ADC; i++) {
+ int val;
+
+ val = smm665_read16(client, SMM665_LIMIT_BASE + i * 8);
+ if (unlikely(val < 0))
+ goto out_unregister;
+ data->critical_min_limit[i] = data->alarm_min_limit[i]
+ = smm665_convert(val, i);
+ val = smm665_read16(client, SMM665_LIMIT_BASE + i * 8 + 2);
+ if (unlikely(val < 0))
+ goto out_unregister;
+ if (smm665_is_critical(val))
+ data->critical_min_limit[i] = smm665_convert(val, i);
+ else
+ data->alarm_min_limit[i] = smm665_convert(val, i);
+ val = smm665_read16(client, SMM665_LIMIT_BASE + i * 8 + 4);
+ if (unlikely(val < 0))
+ goto out_unregister;
+ data->critical_max_limit[i] = data->alarm_max_limit[i]
+ = smm665_convert(val, i);
+ val = smm665_read16(client, SMM665_LIMIT_BASE + i * 8 + 6);
+ if (unlikely(val < 0))
+ goto out_unregister;
+ if (smm665_is_critical(val))
+ data->critical_max_limit[i] = smm665_convert(val, i);
+ else
+ data->alarm_max_limit[i] = smm665_convert(val, i);
+ }
+
+ /* Register sysfs hooks */
+ ret = sysfs_create_group(&client->dev.kobj, &smm665_group);
+ if (ret)
+ goto out_unregister;
+
+ data->hwmon_dev = hwmon_device_register(&client->dev);
+ if (IS_ERR(data->hwmon_dev)) {
+ ret = PTR_ERR(data->hwmon_dev);
+ goto out_remove_group;
+ }
+
+ return 0;
+
+out_remove_group:
+ sysfs_remove_group(&client->dev.kobj, &smm665_group);
+out_unregister:
+ i2c_unregister_device(data->cmdreg);
+out_kfree:
+ kfree(data);
+out_return:
+ return ret;
+}
+
+static int smm665_remove(struct i2c_client *client)
+{
+ struct smm665_data *data = i2c_get_clientdata(client);
+
+ i2c_unregister_device(data->cmdreg);
+ hwmon_device_unregister(data->hwmon_dev);
+ sysfs_remove_group(&client->dev.kobj, &smm665_group);
+
+ kfree(data);
+
+ return 0;
+}
+
+static const struct i2c_device_id smm665_id[] = {
+ {"smm465", smm465},
+ {"smm665", smm665},
+ {"smm665c", smm665c},
+ {"smm764", smm764},
+ {"smm766", smm766},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, smm665_id);
+
+/* This is the driver that will be inserted */
+static struct i2c_driver smm665_driver = {
+ .driver = {
+ .name = "smm665",
+ },
+ .probe = smm665_probe,
+ .remove = smm665_remove,
+ .id_table = smm665_id,
+};
+
+static int __init smm665_init(void)
+{
+ return i2c_add_driver(&smm665_driver);
+}
+
+static void __exit smm665_exit(void)
+{
+ i2c_del_driver(&smm665_driver);
+}
+
+MODULE_AUTHOR("Guenter Roeck");
+MODULE_DESCRIPTION("SMM665 driver");
+MODULE_LICENSE("GPL");
+
+module_init(smm665_init);
+module_exit(smm665_exit);
--
1.7.0.87.g0901d

2010-06-25 14:42:38

by Guenter Roeck

[permalink] [raw]
Subject: [PATCH v4 3/3] hwmon: Update MAINTAINERS for smm665 driver

Signed-off-by: Guenter Roeck <[email protected]>
---
MAINTAINERS | 7 +++++++
1 files changed, 7 insertions(+), 0 deletions(-)

diff --git a/MAINTAINERS b/MAINTAINERS
index 6d119c9..1c196ad 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5205,6 +5205,13 @@ M: Nicolas Pitre <[email protected]>
S: Odd Fixes
F: drivers/net/smc91x.*

+SMM665 HARDWARE MONITOR DRIVER
+M: Guenter Roeck <[email protected]>
+L: [email protected]
+S: Maintained
+F: Documentation/hwmon/smm665
+F: drivers/hwmon/smm665.c
+
SMSC47B397 HARDWARE MONITOR DRIVER
M: "Mark M. Hoffman" <[email protected]>
L: [email protected]
--
1.7.0.87.g0901d

2010-06-25 16:03:21

by Jonathan Cameron

[permalink] [raw]
Subject: Re: [lm-sensors] [PATCH v4 1/3] hwmon: Driver for SMM665 Six-Channel Active DC Output Controller/Monitor

On 06/25/10 15:40, Guenter Roeck wrote:
> This driver adds support for the monitoring features of the Summit
> Microelectronics SMM665 Six-Channel Active DC Output Controller/Monitor.
>

One out of date comment given you have now added the api you say
might be added in future ;)

Otherwise looks good to me,
> Signed-off-by: Guenter Roeck <[email protected]>
Acked-by: Jonathan Cameron <[email protected]>
> ---
> drivers/hwmon/Kconfig | 15 +
> drivers/hwmon/Makefile | 1 +
> drivers/hwmon/smm665.c | 748 ++++++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 764 insertions(+), 0 deletions(-)
> create mode 100644 drivers/hwmon/smm665.c
>
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index e19cf8e..c45c17e 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -739,6 +739,21 @@ config SENSORS_SIS5595
> This driver can also be built as a module. If so, the module
> will be called sis5595.
>
> +config SENSORS_SMM665
> + tristate "Summit Microelectronics SMM665"
> + depends on I2C && EXPERIMENTAL
> + default n
> + help
> + If you say yes here you get support for the hardware monitoring
> + features of the Summit Microelectronics SMM665/SMM665B Six-Channel
> + Active DC Output Controller / Monitor.
> +
> + Other supported chips are SMM465, SMM665C, SMM764, and SMM766.
> + Support for those chips is untested.
> +
> + This driver can also be built as a module. If so, the module will
> + be called smm665.
> +
> config SENSORS_DME1737
> tristate "SMSC DME1737, SCH311x and compatibles"
> depends on I2C && EXPERIMENTAL
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index 2138ceb..55b0940 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -86,6 +86,7 @@ obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o
> obj-$(CONFIG_SENSORS_S3C) += s3c-hwmon.o
> obj-$(CONFIG_SENSORS_SHT15) += sht15.o
> obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o
> +obj-$(CONFIG_SENSORS_SMM665) += smm665.o
> obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o
> obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o
> obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o
> diff --git a/drivers/hwmon/smm665.c b/drivers/hwmon/smm665.c
> new file mode 100644
> index 0000000..760afba
> --- /dev/null
> +++ b/drivers/hwmon/smm665.c
> @@ -0,0 +1,748 @@
> +/*
> + * Driver for SMM665 Power Controller / Monitor
> + *
> + * 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; version 2 of the License.
> + *
> + * This driver should also work for SMM465, SMM764, and SMM766, but is untested
> + * for those chips. Only monitoring functionality is implemented.
> + *
> + * Datasheets:
> + * http://www.summitmicro.com/prod_select/summary/SMM665/SMM665B_2089_20.pdf
> + * http://www.summitmicro.com/prod_select/summary/SMM766B/SMM766B_2122.pdf
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/err.h>
> +#include <linux/slab.h>
> +#include <linux/i2c.h>
> +#include <linux/hwmon.h>
> +#include <linux/hwmon-sysfs.h>
> +#include <linux/delay.h>
> +
> +/* Internal reference voltage (VREF, x 1000 */
> +#define SMM665_VREF_ADC_X1000 1250
> +
> +/* module parameters */
> +static int vref = SMM665_VREF_ADC_X1000;
> +module_param(vref, int, 0);
> +MODULE_PARM_DESC(vref, "Reference voltage in mV");
> +
> +enum chips { smm465, smm665, smm665c, smm764, smm766 };
> +
> +/*
> + * ADC channel addresses
> + */
> +#define SMM665_MISC16_ADC_DATA_A 0x00
> +#define SMM665_MISC16_ADC_DATA_B 0x01
> +#define SMM665_MISC16_ADC_DATA_C 0x02
> +#define SMM665_MISC16_ADC_DATA_D 0x03
> +#define SMM665_MISC16_ADC_DATA_E 0x04
> +#define SMM665_MISC16_ADC_DATA_F 0x05
> +#define SMM665_MISC16_ADC_DATA_VDD 0x06
> +#define SMM665_MISC16_ADC_DATA_12V 0x07
> +#define SMM665_MISC16_ADC_DATA_INT_TEMP 0x08
> +#define SMM665_MISC16_ADC_DATA_AIN1 0x09
> +#define SMM665_MISC16_ADC_DATA_AIN2 0x0a
> +
> +/*
> + * Command registers
> + */
> +#define SMM665_MISC8_CMD_STS 0x80
> +#define SMM665_MISC8_STATUS1 0x81
> +#define SMM665_MISC8_STATUSS2 0x82
> +#define SMM665_MISC8_IO_POLARITY 0x83
> +#define SMM665_MISC8_PUP_POLARITY 0x84
> +#define SMM665_MISC8_ADOC_STATUS1 0x85
> +#define SMM665_MISC8_ADOC_STATUS2 0x86
> +#define SMM665_MISC8_WRITE_PROT 0x87
> +#define SMM665_MISC8_STS_TRACK 0x88
> +
> +/*
> + * Configuration registers and register groups
> + */
> +#define SMM665_ADOC_ENABLE 0x0d
> +#define SMM665_LIMIT_BASE 0x80 /* First limit register */
> +
> +/*
> + * Limit register bit masks
> + */
> +#define SMM665_TRIGGER_RST 0x8000
> +#define SMM665_TRIGGER_HEALTHY 0x4000
> +#define SMM665_TRIGGER_POWEROFF 0x2000
> +#define SMM665_TRIGGER_SHUTDOWN 0x1000
> +#define SMM665_ADC_MASK 0x03ff
> +
> +#define smm665_is_critical(lim) ((lim) & (SMM665_TRIGGER_RST \
> + | SMM665_TRIGGER_POWEROFF \
> + | SMM665_TRIGGER_SHUTDOWN))
> +/*
> + * Fault register bit definitions
> + * Values are merged from status registers 1/2,
> + * with status register 1 providing the upper 8 bits.
> + */
> +#define SMM665_FAULT_A 0x0001
> +#define SMM665_FAULT_B 0x0002
> +#define SMM665_FAULT_C 0x0004
> +#define SMM665_FAULT_D 0x0008
> +#define SMM665_FAULT_E 0x0010
> +#define SMM665_FAULT_F 0x0020
> +#define SMM665_FAULT_VDD 0x0040
> +#define SMM665_FAULT_12V 0x0080
> +#define SMM665_FAULT_TEMP 0x0100
> +#define SMM665_FAULT_AIN1 0x0200
> +#define SMM665_FAULT_AIN2 0x0400
> +
> +/*
> + * I2C Register addresses
> + *
> + * The configuration register needs to be the configured base register.
> + * The command/status register address is derived from it.
> + */
> +#define SMM665_REGMASK 0x78
> +#define SMM665_CMDREG_BASE 0x48
> +#define SMM665_CONFREG_BASE 0x50
> +
> +/*
> + * Equations given by chip manufacturer to calculate voltage/temperature values
> + * vref = Reference voltage on VREF_ADC pin (module parameter)
> + * adc = 10bit ADC value read back from registers
> + */
> +
> +/* Voltage A-F and VDD */
> +#define SMM665_VMON_ADC_TO_VOLTS(adc) ((adc) * vref / 256)
> +
> +/* Voltage 12VIN */
> +#define SMM665_12VIN_ADC_TO_VOLTS(adc) ((adc) * vref * 3 / 256)
> +
> +/* Voltage AIN1, AIN2 */
> +#define SMM665_AIN_ADC_TO_VOLTS(adc) ((adc) * vref / 512)
> +
> +/* Temp Sensor */
> +#define SMM665_TEMP_ADC_TO_CELSIUS(adc) ((adc) <= 511) ? \
> + ((int)(adc) * 1000 / 4) : \
> + (((int)(adc) - 0x400) * 1000 / 4)
> +
> +#define SMM665_NUM_ADC 11
> +
> +/*
> + * Chip dependent ADC conversion time, in uS
> + */
> +#define SMM665_ADC_WAIT_SMM665 70
> +#define SMM665_ADC_WAIT_SMM766 185
> +
> +struct smm665_data {
> + enum chips type;
> + int conversion_time; /* ADC conversion time */
> + struct device *hwmon_dev;
> + struct mutex update_lock;
> + bool valid;
> + unsigned long last_updated; /* in jiffies */
> + u16 adc[SMM665_NUM_ADC]; /* adc values (raw) */
> + u16 faults; /* fault status */
> + /* The following values are in mV */
> + int critical_min_limit[SMM665_NUM_ADC];
> + int alarm_min_limit[SMM665_NUM_ADC];
> + int critical_max_limit[SMM665_NUM_ADC];
> + int alarm_max_limit[SMM665_NUM_ADC];
> + struct i2c_client *cmdreg;
> +};
> +
> +/*
> + * smm665_read16()
> + *
> + * Read 16 bit value from <reg>, <reg+1>. Upper 8 bits are in <reg>.
> + */
> +static int smm665_read16(struct i2c_client *client, int reg)
> +{
> + int rv, val;
> +
> + rv = i2c_smbus_read_byte_data(client, reg);
> + if (rv < 0)
> + return rv;
> + val = rv << 8;
> + rv = i2c_smbus_read_byte_data(client, reg + 1);
> + if (rv < 0)
> + return rv;
> + val |= rv;
> + return val;
> +}
> +
> +/*
> + * Read adc value.
> + */
> +static int smm665_read_adc(struct smm665_data *data, int adc)
> +{
> + struct i2c_client *client = data->cmdreg;
> + int rv;
> + int radc;
> +
> + /*
> + * Algorithm for reading ADC, per SMM665 datasheet
> + *
> + * {[S][addr][W][Ack]} {[offset][Ack]} {[S][addr][R][Nack]}
> + * [wait conversion time]
> + * {[S][addr][R][Ack]} {[datahi][Ack]} {[datalo][Ack][P]}
> + *
> + * To implement the first part of this exchange,
> + * do a full read transaction and expect a failure/Nack.
> + * This sets up the address pointer on the SMM665
> + * and starts the ADC conversion.
> + * Then do a two-byte read transaction.
> + */
> + rv = i2c_smbus_read_byte_data(client, adc << 3);
> + if (rv != -ENXIO) {
> + /*
> + * We expect ENXIO to reflect NACK
> + * (per Documentation/i2c/fault-codes).
> + * Everything else is an error.
> + */
> + dev_dbg(&client->dev,
> + "Unexpected return code %d when setting ADC index", rv);
> + return (rv < 0) ? rv : -EIO;
> + }
> +
> + udelay(data->conversion_time);
> +
> + /*
> + * Now read two bytes.
> + *
> + * Neither i2c_smbus_read_byte() nor
> + * i2c_smbus_read_block_data() worked here,
> + * so use i2c_smbus_read_word_data() instead.
> + * We could also try to use i2c_master_recv(),
> + * but that is not always supported.
> + */
> + rv = i2c_smbus_read_word_data(client, 0);
> + if (rv < 0) {
> + dev_dbg(&client->dev, "Failed to read ADC value: error %d", rv);
> + return -1;
> + }
> + /*
> + * Validate/verify readback adc channel (in bit 11..14).
> + * High byte is in lower 8 bit of rv, so only shift by 3.
> + */
> + radc = (rv >> 3) & 0x0f;
> + if (radc != adc) {
> + dev_dbg(&client->dev, "Unexpected RADC: Expected %d got %d",
> + adc, radc);
> + return -EIO;
> + }
> + /*
> + * Chip replies with H/L, while SMBus expects L/H.
> + * Thus, byte order is reversed, and we have to swap
> + * the result.
> + */
> + rv = swab16(rv) & SMM665_ADC_MASK;
> +
> + return rv;
> +}
> +
> +static struct smm665_data *smm665_update_device(struct device *dev)
> +{
> + struct i2c_client *client = to_i2c_client(dev);
> + struct smm665_data *data = i2c_get_clientdata(client);
> + struct smm665_data *ret = data;
> +
> + mutex_lock(&data->update_lock);
> +
> + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
> + int i, val;
> +
> + /*
> + * read status registers
> + */
> + val = smm665_read16(client, SMM665_MISC8_STATUS1);
> + if (unlikely(val < 0)) {
> + ret = ERR_PTR(val);
> + goto abort;
> + }
> + data->faults = val;
> +
> + /* Read adc registers */
> + for (i = 0; i < SMM665_NUM_ADC; i++) {
> + val = smm665_read_adc(data, i);
> + if (unlikely(val < 0)) {
> + ret = ERR_PTR(val);
> + goto abort;
> + }
> + data->adc[i] = val;
> + }
> + data->last_updated = jiffies;
> + data->valid = 1;
> + }
> +abort:
> + mutex_unlock(&data->update_lock);
> + return ret;
> +}
> +
> +/* Return converted value from given adc */
> +static int smm665_convert(u16 adcval, int index)
> +{
> + int val = 0;
> +
> + switch (index) {
> + case SMM665_MISC16_ADC_DATA_12V:
> + val = SMM665_12VIN_ADC_TO_VOLTS(adcval & SMM665_ADC_MASK);
> + break;
> +
> + case SMM665_MISC16_ADC_DATA_VDD:
> + case SMM665_MISC16_ADC_DATA_A:
> + case SMM665_MISC16_ADC_DATA_B:
> + case SMM665_MISC16_ADC_DATA_C:
> + case SMM665_MISC16_ADC_DATA_D:
> + case SMM665_MISC16_ADC_DATA_E:
> + case SMM665_MISC16_ADC_DATA_F:
> + val = SMM665_VMON_ADC_TO_VOLTS(adcval & SMM665_ADC_MASK);
> + break;
> +
> + case SMM665_MISC16_ADC_DATA_AIN1:
> + case SMM665_MISC16_ADC_DATA_AIN2:
> + val = SMM665_AIN_ADC_TO_VOLTS(adcval & SMM665_ADC_MASK);
> + break;
> +
> + case SMM665_MISC16_ADC_DATA_INT_TEMP:
> + val = SMM665_TEMP_ADC_TO_CELSIUS(adcval & SMM665_ADC_MASK);
> + break;
> +
> + default:
> + /* If we get here, the developer messed up */
> + WARN_ON_ONCE(1);
> + break;
> + }
> +
> + return val;
> +}
> +
> +static int smm665_get_min(struct device *dev, int index)
> +{
> + struct i2c_client *client = to_i2c_client(dev);
> + struct smm665_data *data = i2c_get_clientdata(client);
> +
> + return data->alarm_min_limit[index];
> +}
> +
> +static int smm665_get_max(struct device *dev, int index)
> +{
> + struct i2c_client *client = to_i2c_client(dev);
> + struct smm665_data *data = i2c_get_clientdata(client);
> +
> + return data->alarm_max_limit[index];
> +}
> +
> +static int smm665_get_lcrit(struct device *dev, int index)
> +{
> + struct i2c_client *client = to_i2c_client(dev);
> + struct smm665_data *data = i2c_get_clientdata(client);
> +
> + return data->critical_min_limit[index];
> +}
> +
> +static int smm665_get_crit(struct device *dev, int index)
> +{
> + struct i2c_client *client = to_i2c_client(dev);
> + struct smm665_data *data = i2c_get_clientdata(client);
> +
> + return data->critical_max_limit[index];
> +}
> +
> +static ssize_t smm665_show_fault(struct device *dev,
> + struct device_attribute *da, char *buf)
> +{
> + struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
> + struct smm665_data *data = smm665_update_device(dev);
> + int val = 0;
> +
> + if (IS_ERR(data))
> + return PTR_ERR(data);
> +
> + if (data->faults & (1 << attr->index))
> + val = 1;
> +
> + return snprintf(buf, PAGE_SIZE, "%d\n", val);
> +}
> +
> +static ssize_t smm665_show_input(struct device *dev,
> + struct device_attribute *da, char *buf)
> +{
> + struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
> + struct smm665_data *data = smm665_update_device(dev);
> + int adc = attr->index;
> + int val;
> +
> + if (IS_ERR(data))
> + return PTR_ERR(data);
> +
> + val = smm665_convert(data->adc[adc], adc);
> + return snprintf(buf, PAGE_SIZE, "%d\n", val);
> +}
> +
> +#define SMM665_SHOW(what) \
> + static ssize_t smm665_show_##what(struct device *dev, \
> + struct device_attribute *da, char *buf) \
> +{ \
> + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); \
> + const int val = smm665_get_##what(dev, attr->index); \
> + return snprintf(buf, PAGE_SIZE, "%d\n", val); \
> +}
> +
> +SMM665_SHOW(min);
> +SMM665_SHOW(max);
> +SMM665_SHOW(lcrit);
> +SMM665_SHOW(crit);
> +
> +/* These macros are used below in constructing device attribute objects
> + * for use with sysfs_create_group() to make a sysfs device file
> + * for each register.
> + */
> +
> +#define SMM665_ATTR(name, type, cmd_idx) \
> + static SENSOR_DEVICE_ATTR(name##_##type, S_IRUGO, \
> + smm665_show_##type, NULL, cmd_idx)
> +
> +/* Construct a sensor_device_attribute structure for each register */
> +
> +/* Input voltages */
> +SMM665_ATTR(in1, input, SMM665_MISC16_ADC_DATA_12V);
> +SMM665_ATTR(in2, input, SMM665_MISC16_ADC_DATA_VDD);
> +SMM665_ATTR(in3, input, SMM665_MISC16_ADC_DATA_A);
> +SMM665_ATTR(in4, input, SMM665_MISC16_ADC_DATA_B);
> +SMM665_ATTR(in5, input, SMM665_MISC16_ADC_DATA_C);
> +SMM665_ATTR(in6, input, SMM665_MISC16_ADC_DATA_D);
> +SMM665_ATTR(in7, input, SMM665_MISC16_ADC_DATA_E);
> +SMM665_ATTR(in8, input, SMM665_MISC16_ADC_DATA_F);
> +SMM665_ATTR(in9, input, SMM665_MISC16_ADC_DATA_AIN1);
> +SMM665_ATTR(in10, input, SMM665_MISC16_ADC_DATA_AIN2);
> +
> +/* Input voltages min */
> +SMM665_ATTR(in1, min, SMM665_MISC16_ADC_DATA_12V);
> +SMM665_ATTR(in2, min, SMM665_MISC16_ADC_DATA_VDD);
> +SMM665_ATTR(in3, min, SMM665_MISC16_ADC_DATA_A);
> +SMM665_ATTR(in4, min, SMM665_MISC16_ADC_DATA_B);
> +SMM665_ATTR(in5, min, SMM665_MISC16_ADC_DATA_C);
> +SMM665_ATTR(in6, min, SMM665_MISC16_ADC_DATA_D);
> +SMM665_ATTR(in7, min, SMM665_MISC16_ADC_DATA_E);
> +SMM665_ATTR(in8, min, SMM665_MISC16_ADC_DATA_F);
> +SMM665_ATTR(in9, min, SMM665_MISC16_ADC_DATA_AIN1);
> +SMM665_ATTR(in10, min, SMM665_MISC16_ADC_DATA_AIN2);
> +
> +/* Input voltages max */
> +SMM665_ATTR(in1, max, SMM665_MISC16_ADC_DATA_12V);
> +SMM665_ATTR(in2, max, SMM665_MISC16_ADC_DATA_VDD);
> +SMM665_ATTR(in3, max, SMM665_MISC16_ADC_DATA_A);
> +SMM665_ATTR(in4, max, SMM665_MISC16_ADC_DATA_B);
> +SMM665_ATTR(in5, max, SMM665_MISC16_ADC_DATA_C);
> +SMM665_ATTR(in6, max, SMM665_MISC16_ADC_DATA_D);
> +SMM665_ATTR(in7, max, SMM665_MISC16_ADC_DATA_E);
> +SMM665_ATTR(in8, max, SMM665_MISC16_ADC_DATA_F);
> +SMM665_ATTR(in9, max, SMM665_MISC16_ADC_DATA_AIN1);
> +SMM665_ATTR(in10, max, SMM665_MISC16_ADC_DATA_AIN2);
> +
> +/* Input voltages lcrit */
> +SMM665_ATTR(in1, lcrit, SMM665_MISC16_ADC_DATA_12V);
> +SMM665_ATTR(in2, lcrit, SMM665_MISC16_ADC_DATA_VDD);
> +SMM665_ATTR(in3, lcrit, SMM665_MISC16_ADC_DATA_A);
> +SMM665_ATTR(in4, lcrit, SMM665_MISC16_ADC_DATA_B);
> +SMM665_ATTR(in5, lcrit, SMM665_MISC16_ADC_DATA_C);
> +SMM665_ATTR(in6, lcrit, SMM665_MISC16_ADC_DATA_D);
> +SMM665_ATTR(in7, lcrit, SMM665_MISC16_ADC_DATA_E);
> +SMM665_ATTR(in8, lcrit, SMM665_MISC16_ADC_DATA_F);
> +SMM665_ATTR(in9, lcrit, SMM665_MISC16_ADC_DATA_AIN1);
> +SMM665_ATTR(in10, lcrit, SMM665_MISC16_ADC_DATA_AIN2);
> +
> +/* Input voltages crit */
> +SMM665_ATTR(in1, crit, SMM665_MISC16_ADC_DATA_12V);
> +SMM665_ATTR(in2, crit, SMM665_MISC16_ADC_DATA_VDD);
> +SMM665_ATTR(in3, crit, SMM665_MISC16_ADC_DATA_A);
> +SMM665_ATTR(in4, crit, SMM665_MISC16_ADC_DATA_B);
> +SMM665_ATTR(in5, crit, SMM665_MISC16_ADC_DATA_C);
> +SMM665_ATTR(in6, crit, SMM665_MISC16_ADC_DATA_D);
> +SMM665_ATTR(in7, crit, SMM665_MISC16_ADC_DATA_E);
> +SMM665_ATTR(in8, crit, SMM665_MISC16_ADC_DATA_F);
> +SMM665_ATTR(in9, crit, SMM665_MISC16_ADC_DATA_AIN1);
> +SMM665_ATTR(in10, crit, SMM665_MISC16_ADC_DATA_AIN2);
> +
> +/* Faults */
> +SMM665_ATTR(in1, fault, SMM665_FAULT_12V);
> +SMM665_ATTR(in2, fault, SMM665_FAULT_VDD);
> +SMM665_ATTR(in3, fault, SMM665_FAULT_A);
> +SMM665_ATTR(in4, fault, SMM665_FAULT_B);
> +SMM665_ATTR(in5, fault, SMM665_FAULT_C);
> +SMM665_ATTR(in6, fault, SMM665_FAULT_D);
> +SMM665_ATTR(in7, fault, SMM665_FAULT_E);
> +SMM665_ATTR(in8, fault, SMM665_FAULT_F);
> +SMM665_ATTR(in9, fault, SMM665_FAULT_AIN1);
> +SMM665_ATTR(in10, fault, SMM665_FAULT_AIN2);
> +
> +/* Temperature */
> +SMM665_ATTR(temp1, input, SMM665_MISC16_ADC_DATA_INT_TEMP);
> +SMM665_ATTR(temp1, min, SMM665_MISC16_ADC_DATA_INT_TEMP);
> +SMM665_ATTR(temp1, max, SMM665_MISC16_ADC_DATA_INT_TEMP);
> +SMM665_ATTR(temp1, lcrit, SMM665_MISC16_ADC_DATA_INT_TEMP);
> +SMM665_ATTR(temp1, crit, SMM665_MISC16_ADC_DATA_INT_TEMP);
> +SMM665_ATTR(temp1, fault, SMM665_FAULT_TEMP);
> +
> +/*
> + * Finally, construct an array of pointers to members of the above objects,
> + * as required for sysfs_create_group()
> + */
> +static struct attribute *smm665_attributes[] = {
> + &sensor_dev_attr_in1_input.dev_attr.attr,
> + &sensor_dev_attr_in1_min.dev_attr.attr,
> + &sensor_dev_attr_in1_max.dev_attr.attr,
> + &sensor_dev_attr_in1_lcrit.dev_attr.attr,
> + &sensor_dev_attr_in1_crit.dev_attr.attr,
> + &sensor_dev_attr_in1_fault.dev_attr.attr,
> +
> + &sensor_dev_attr_in2_input.dev_attr.attr,
> + &sensor_dev_attr_in2_min.dev_attr.attr,
> + &sensor_dev_attr_in2_max.dev_attr.attr,
> + &sensor_dev_attr_in2_lcrit.dev_attr.attr,
> + &sensor_dev_attr_in2_crit.dev_attr.attr,
> + &sensor_dev_attr_in2_fault.dev_attr.attr,
> +
> + &sensor_dev_attr_in3_input.dev_attr.attr,
> + &sensor_dev_attr_in3_min.dev_attr.attr,
> + &sensor_dev_attr_in3_max.dev_attr.attr,
> + &sensor_dev_attr_in3_lcrit.dev_attr.attr,
> + &sensor_dev_attr_in3_crit.dev_attr.attr,
> + &sensor_dev_attr_in3_fault.dev_attr.attr,
> +
> + &sensor_dev_attr_in4_input.dev_attr.attr,
> + &sensor_dev_attr_in4_min.dev_attr.attr,
> + &sensor_dev_attr_in4_max.dev_attr.attr,
> + &sensor_dev_attr_in4_lcrit.dev_attr.attr,
> + &sensor_dev_attr_in4_crit.dev_attr.attr,
> + &sensor_dev_attr_in4_fault.dev_attr.attr,
> +
> + &sensor_dev_attr_in5_input.dev_attr.attr,
> + &sensor_dev_attr_in5_min.dev_attr.attr,
> + &sensor_dev_attr_in5_max.dev_attr.attr,
> + &sensor_dev_attr_in5_lcrit.dev_attr.attr,
> + &sensor_dev_attr_in5_crit.dev_attr.attr,
> + &sensor_dev_attr_in5_fault.dev_attr.attr,
> +
> + &sensor_dev_attr_in6_input.dev_attr.attr,
> + &sensor_dev_attr_in6_min.dev_attr.attr,
> + &sensor_dev_attr_in6_max.dev_attr.attr,
> + &sensor_dev_attr_in6_lcrit.dev_attr.attr,
> + &sensor_dev_attr_in6_crit.dev_attr.attr,
> + &sensor_dev_attr_in6_fault.dev_attr.attr,
> +
> + &sensor_dev_attr_in7_input.dev_attr.attr,
> + &sensor_dev_attr_in7_min.dev_attr.attr,
> + &sensor_dev_attr_in7_max.dev_attr.attr,
> + &sensor_dev_attr_in7_lcrit.dev_attr.attr,
> + &sensor_dev_attr_in7_crit.dev_attr.attr,
> + &sensor_dev_attr_in7_fault.dev_attr.attr,
> +
> + &sensor_dev_attr_in8_input.dev_attr.attr,
> + &sensor_dev_attr_in8_min.dev_attr.attr,
> + &sensor_dev_attr_in8_max.dev_attr.attr,
> + &sensor_dev_attr_in8_lcrit.dev_attr.attr,
> + &sensor_dev_attr_in8_crit.dev_attr.attr,
> + &sensor_dev_attr_in8_fault.dev_attr.attr,
> +
> + &sensor_dev_attr_in9_input.dev_attr.attr,
> + &sensor_dev_attr_in9_min.dev_attr.attr,
> + &sensor_dev_attr_in9_max.dev_attr.attr,
> + &sensor_dev_attr_in9_lcrit.dev_attr.attr,
> + &sensor_dev_attr_in9_crit.dev_attr.attr,
> + &sensor_dev_attr_in9_fault.dev_attr.attr,
> +
> + &sensor_dev_attr_in10_input.dev_attr.attr,
> + &sensor_dev_attr_in10_min.dev_attr.attr,
> + &sensor_dev_attr_in10_max.dev_attr.attr,
> + &sensor_dev_attr_in10_lcrit.dev_attr.attr,
> + &sensor_dev_attr_in10_crit.dev_attr.attr,
> + &sensor_dev_attr_in10_fault.dev_attr.attr,
> +
> + &sensor_dev_attr_temp1_input.dev_attr.attr,
> + &sensor_dev_attr_temp1_min.dev_attr.attr,
> + &sensor_dev_attr_temp1_max.dev_attr.attr,
> + &sensor_dev_attr_temp1_lcrit.dev_attr.attr,
> + &sensor_dev_attr_temp1_crit.dev_attr.attr,
> + &sensor_dev_attr_temp1_fault.dev_attr.attr,
> +
> + NULL,
> +};
> +
> +static const struct attribute_group smm665_group = {
> + .attrs = smm665_attributes,
> +};
> +
> +static int smm665_probe(struct i2c_client *client,
> + const struct i2c_device_id *id)
> +{
> + struct i2c_adapter *adapter = client->adapter;
> + struct smm665_data *data;
> + int i, ret;
> +
> + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA
> + | I2C_FUNC_SMBUS_WORD_DATA))
> + return -ENODEV;
> +
> + if (i2c_smbus_read_byte_data(client, SMM665_ADOC_ENABLE) < 0)
> + return -ENODEV;
> +
> + ret = -ENOMEM;
> + data = kzalloc(sizeof(*data), GFP_KERNEL);
> + if (!data)
> + goto out_return;
> +
> + i2c_set_clientdata(client, data);
> + mutex_init(&data->update_lock);
> +
> + data->type = id->driver_data;
> + data->cmdreg = i2c_new_dummy(adapter, (client->addr & ~SMM665_REGMASK)
> + | SMM665_CMDREG_BASE);
> + if (!data->cmdreg)
> + goto out_kfree;
> +
> + switch (data->type) {
> + case smm465:
> + case smm665:
> + data->conversion_time = SMM665_ADC_WAIT_SMM665;
> + break;
> + case smm665c:
> + case smm764:
> + case smm766:
> + data->conversion_time = SMM665_ADC_WAIT_SMM766;
> + break;
> + }
> +
> + ret = -ENODEV;
> + if (i2c_smbus_read_byte_data(data->cmdreg, SMM665_MISC8_CMD_STS) < 0)
> + goto out_unregister;
> +
> + /*
> + * Read limits.
> + *
> + * Limit registers start with register SMM665_LIMIT_BASE.
> + * Each channel uses 8 registers, providing four limit values
> + * per channel. Each limit value requires two registers, with the
> + * high byte in the first register and the low byte in the second
> + * register. The first two limits are under limit values, followed
> + * by two over limit values.
> + *
> + * Limit register order matches the ADC register order, so we use
> + * ADC register defines throughout the code to index limit registers.
> + *
> + * We save the first retrieved value both as "critical" and "alarm"
> + * value. The second value overwrites either the critical or the
> + * alarm value, depending on its configuration. This ensures that both
> + * critical and alarm values are initialized, even if both registers are
> + * configured as critical or non-critical.
> + *
> + * Note: Critical values for voltage channels are saved, even though
> + * this information is currently not used by the driver. This is mostly
> + * for consistency, though it might eventually be useful if future APIs
> + * support reporting "critical" voltage values.
Comment is out of date. Looks like you just added the api ;)

> + */
> + for (i = 0; i < SMM665_NUM_ADC; i++) {
> + int val;
> +
> + val = smm665_read16(client, SMM665_LIMIT_BASE + i * 8);
> + if (unlikely(val < 0))
> + goto out_unregister;
> + data->critical_min_limit[i] = data->alarm_min_limit[i]
> + = smm665_convert(val, i);
> + val = smm665_read16(client, SMM665_LIMIT_BASE + i * 8 + 2);
> + if (unlikely(val < 0))
> + goto out_unregister;
> + if (smm665_is_critical(val))
> + data->critical_min_limit[i] = smm665_convert(val, i);
> + else
> + data->alarm_min_limit[i] = smm665_convert(val, i);
> + val = smm665_read16(client, SMM665_LIMIT_BASE + i * 8 + 4);
> + if (unlikely(val < 0))
> + goto out_unregister;
> + data->critical_max_limit[i] = data->alarm_max_limit[i]
> + = smm665_convert(val, i);
> + val = smm665_read16(client, SMM665_LIMIT_BASE + i * 8 + 6);
> + if (unlikely(val < 0))
> + goto out_unregister;
> + if (smm665_is_critical(val))
> + data->critical_max_limit[i] = smm665_convert(val, i);
> + else
> + data->alarm_max_limit[i] = smm665_convert(val, i);
> + }
> +
> + /* Register sysfs hooks */
> + ret = sysfs_create_group(&client->dev.kobj, &smm665_group);
> + if (ret)
> + goto out_unregister;
> +
> + data->hwmon_dev = hwmon_device_register(&client->dev);
> + if (IS_ERR(data->hwmon_dev)) {
> + ret = PTR_ERR(data->hwmon_dev);
> + goto out_remove_group;
> + }
> +
> + return 0;
> +
> +out_remove_group:
> + sysfs_remove_group(&client->dev.kobj, &smm665_group);
> +out_unregister:
> + i2c_unregister_device(data->cmdreg);
> +out_kfree:
> + kfree(data);
> +out_return:
> + return ret;
> +}
> +
> +static int smm665_remove(struct i2c_client *client)
> +{
> + struct smm665_data *data = i2c_get_clientdata(client);
> +
> + i2c_unregister_device(data->cmdreg);
> + hwmon_device_unregister(data->hwmon_dev);
> + sysfs_remove_group(&client->dev.kobj, &smm665_group);
> +
> + kfree(data);
> +
> + return 0;
> +}
> +
> +static const struct i2c_device_id smm665_id[] = {
> + {"smm465", smm465},
> + {"smm665", smm665},
> + {"smm665c", smm665c},
> + {"smm764", smm764},
> + {"smm766", smm766},
> + {}
> +};
> +
> +MODULE_DEVICE_TABLE(i2c, smm665_id);
> +
> +/* This is the driver that will be inserted */
> +static struct i2c_driver smm665_driver = {
> + .driver = {
> + .name = "smm665",
> + },
> + .probe = smm665_probe,
> + .remove = smm665_remove,
> + .id_table = smm665_id,
> +};
> +
> +static int __init smm665_init(void)
> +{
> + return i2c_add_driver(&smm665_driver);
> +}
> +
> +static void __exit smm665_exit(void)
> +{
> + i2c_del_driver(&smm665_driver);
> +}
> +
> +MODULE_AUTHOR("Guenter Roeck");
> +MODULE_DESCRIPTION("SMM665 driver");
> +MODULE_LICENSE("GPL");
> +
> +module_init(smm665_init);
> +module_exit(smm665_exit);

2010-06-25 16:04:57

by Jonathan Cameron

[permalink] [raw]
Subject: Re: [lm-sensors] [PATCH v2 2/3] hwmon: SMM665 driver documentation

On 06/25/10 15:40, Guenter Roeck wrote:
Looks good to me, if you are respinning for any reason, perhaps
say where the summit website is?

Acked-by: Jonathan Cameron <[email protected]>
> Signed-off-by: Guenter Roeck <[email protected]>
> ---
> Documentation/hwmon/smm665 | 151 ++++++++++++++++++++++++++++++++++++++++++++
> 1 files changed, 151 insertions(+), 0 deletions(-)
> create mode 100644 Documentation/hwmon/smm665
>
> diff --git a/Documentation/hwmon/smm665 b/Documentation/hwmon/smm665
> new file mode 100644
> index 0000000..8c08f63
> --- /dev/null
> +++ b/Documentation/hwmon/smm665
> @@ -0,0 +1,151 @@
> +Kernel driver smm665
> +====================
> +
> +Supported chips:
> + * Summit Microelectronics SMM465
> + Prefix: 'smm465'
> + Addresses scanned: -
> + Datasheet: Publicly available at the Summit Microelectronics website.
> + * Summit Microelectronics SMM665, SMM665B
> + Prefix: 'smm665'
> + Addresses scanned: -
> + Datasheet: Publicly available at the Summit Microelectronics website.
> + * Summit Microelectronics SMM665C
> + Prefix: 'smm665c'
> + Addresses scanned: -
> + Datasheet: Publicly available at the Summit Microelectronics website.
> + * Summit Microelectronics SMM764
> + Prefix: 'smm764'
> + Addresses scanned: -
> + Datasheet: Publicly available at the Summit Microelectronics website.
> + * Summit Microelectronics SMM766
> + Prefix: 'smm766'
> + Addresses scanned: -
> + Datasheet: Publicly available at the Summit Microelectronics website.
> +
> +Author: Guenter Roeck <[email protected]>
> +
> +
> +Module Parameters
> +-----------------
> +
> +* vref: int
> + Default: 1250 (mV)
> + Reference voltage on VREF_ADC pin in mV. It should not be necessary to set
> + this parameter unless a non-default reference voltage is used.
> +
> +
> +Description
> +-----------
> +
> +[From datasheet] The SMM665 is an Active DC Output power supply Controller
> +that monitors, margins and cascade sequences power. The part monitors six
> +power supply channels as well as VDD, 12V input, two general-purpose analog
> +inputs and an internal temperature sensor using a 10-bit ADC.
> +
> +Each monitored channel has its own high and low limits, plus a critical
> +limit.
> +
> +Support for SMM465, SMM764, and SMM766 has been implemented but is untested.
> +
> +
> +Usage Notes
> +-----------
> +
> +This driver does not probe for devices, since there is no register which
> +can be safely used to identify the chip. You will have to instantiate
> +the devices explicitly. When instantiating the device, you have to specify
> +its configuration register address.
> +
> +Example: the following will load the driver for an SMM665 at address 0x57
> +on I2C bus #1:
> +$ modprobe smm665
> +$ echo smm665 0x57 > /sys/bus/i2c/devices/i2c-1/new_device
> +
> +
> +Sysfs entries
> +-------------
> +
> +This driver uses the values in the datasheet to convert ADC register values
> +into the values specified in the sysfs-interface document. All attributes are
> +read only.
> +
> +Min, max, lcrit, and crit values are used by the chip to trigger external signals
> +and/or other activity. Triggered signals can include HEALTHY, RST, Power Off,
> +or Fault depending on the chip configuration. The driver reports values as lcrit
> +or crit if exceeding the limits triggers RST, Power Off, or Fault, and as min or
> +max otherwise. For details please see the SMM665 datasheet.
> +
> +For SMM465 and SMM764, values for Channel E and F are reported but undefined.
> +
> +in1_input 12V input voltage (mV)
> +in2_input 3.3V (VDD) input voltage (mV)
> +in3_input Channel A voltage (mV)
> +in4_input Channel B voltage (mV)
> +in5_input Channel C voltage (mV)
> +in6_input Channel D voltage (mV)
> +in7_input Channel E voltage (mV)
> +in8_input Channel F voltage (mV)
> +in9_input AIN1 voltage (mV)
> +in10_input AIN2 voltage (mV)
> +
> +in1_min 12v input minimum voltage (mV)
> +in2_min 3.3V (VDD) input minimum voltage (mV)
> +in3_min Channel A minimum voltage (mV)
> +in4_min Channel B minimum voltage (mV)
> +in5_min Channel C minimum voltage (mV)
> +in6_min Channel D minimum voltage (mV)
> +in7_min Channel E minimum voltage (mV)
> +in8_min Channel F minimum voltage (mV)
> +in9_min AIN1 minimum voltage (mV)
> +in10_min AIN2 minimum voltage (mV)
> +
> +in1_max 12v input maximum voltage (mV)
> +in2_max 3.3V (VDD) input maximum voltage (mV)
> +in3_max Channel A maximum voltage (mV)
> +in4_max Channel B maximum voltage (mV)
> +in5_max Channel C maximum voltage (mV)
> +in6_max Channel D maximum voltage (mV)
> +in7_max Channel E maximum voltage (mV)
> +in8_max Channel F maximum voltage (mV)
> +in9_max AIN1 maximum voltage (mV)
> +in10_max AIN2 maximum voltage (mV)
> +
> +in1_lcrit 12v input critical minimum voltage (mV)
> +in2_lcrit 3.3V (VDD) input critical minimum voltage (mV)
> +in3_lcrit Channel A critical minimum voltage (mV)
> +in4_lcrit Channel B critical minimum voltage (mV)
> +in5_lcrit Channel C critical minimum voltage (mV)
> +in6_lcrit Channel D critical minimum voltage (mV)
> +in7_lcrit Channel E critical minimum voltage (mV)
> +in8_lcrit Channel F critical minimum voltage (mV)
> +in9_lcrit AIN1 critical minimum voltage (mV)
> +in10_lcrit AIN2 critical minimum voltage (mV)
> +
> +in1_crit 12v input critical maximum voltage (mV)
> +in2_crit 3.3V (VDD) input critical maximum voltage (mV)
> +in3_crit Channel A critical maximum voltage (mV)
> +in4_crit Channel B critical maximum voltage (mV)
> +in5_crit Channel C critical maximum voltage (mV)
> +in6_crit Channel D critical maximum voltage (mV)
> +in7_crit Channel E critical maximum voltage (mV)
> +in8_crit Channel F critical maximum voltage (mV)
> +in9_crit AIN1 critical maximum voltage (mV)
> +in10_crit AIN2 critical maximum voltage (mV)
> +
> +in1_fault 12v input fault
> +in2_fault 3.3V (VDD) input fault
> +in3_fault Channel A fault
> +in4_fault Channel B fault
> +in5_fault Channel C fault
> +in6_fault Channel D fault
> +in7_fault Channel E fault
> +in8_fault Channel F fault
> +in9_fault AIN1 fault
> +in10_fault AIN2 fault
> +
> +temp1_input Chip tempererature
> +temp1_min Mimimum chip tempererature
> +temp1_max Maximum chip tempererature
> +temp1_crit Critical chip tempererature
> +temp1_fault Temperature fault

2010-06-25 17:38:46

by Guenter Roeck

[permalink] [raw]
Subject: Re: [lm-sensors] [PATCH v4 1/3] hwmon: Driver for SMM665 Six-Channel Active DC Output Controller/Monitor

On Fri, 2010-06-25 at 12:06 -0400, Jonathan Cameron wrote:
> On 06/25/10 15:40, Guenter Roeck wrote:
> > This driver adds support for the monitoring features of the Summit
> > Microelectronics SMM665 Six-Channel Active DC Output Controller/Monitor.
> >
>
> One out of date comment given you have now added the api you say
> might be added in future ;)
>
> Otherwise looks good to me,
> > Signed-off-by: Guenter Roeck <[email protected]>
> Acked-by: Jonathan Cameron <[email protected]>

Again, thanks a lot for the review. I'll remove the obsolete comment and
re-submit with your Acked-by, assuming that is ok with you.

Guenter

> > ---
> > drivers/hwmon/Kconfig | 15 +
> > drivers/hwmon/Makefile | 1 +
> > drivers/hwmon/smm665.c | 748 ++++++++++++++++++++++++++++++++++++++++++++++++
> > 3 files changed, 764 insertions(+), 0 deletions(-)
> > create mode 100644 drivers/hwmon/smm665.c
> >
> > diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> > index e19cf8e..c45c17e 100644
> > --- a/drivers/hwmon/Kconfig
> > +++ b/drivers/hwmon/Kconfig
> > @@ -739,6 +739,21 @@ config SENSORS_SIS5595
> > This driver can also be built as a module. If so, the module
> > will be called sis5595.
> >
> > +config SENSORS_SMM665
> > + tristate "Summit Microelectronics SMM665"
> > + depends on I2C && EXPERIMENTAL
> > + default n
> > + help
> > + If you say yes here you get support for the hardware monitoring
> > + features of the Summit Microelectronics SMM665/SMM665B Six-Channel
> > + Active DC Output Controller / Monitor.
> > +
> > + Other supported chips are SMM465, SMM665C, SMM764, and SMM766.
> > + Support for those chips is untested.
> > +
> > + This driver can also be built as a module. If so, the module will
> > + be called smm665.
> > +
> > config SENSORS_DME1737
> > tristate "SMSC DME1737, SCH311x and compatibles"
> > depends on I2C && EXPERIMENTAL
> > diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> > index 2138ceb..55b0940 100644
> > --- a/drivers/hwmon/Makefile
> > +++ b/drivers/hwmon/Makefile
> > @@ -86,6 +86,7 @@ obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o
> > obj-$(CONFIG_SENSORS_S3C) += s3c-hwmon.o
> > obj-$(CONFIG_SENSORS_SHT15) += sht15.o
> > obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o
> > +obj-$(CONFIG_SENSORS_SMM665) += smm665.o
> > obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o
> > obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o
> > obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o
> > diff --git a/drivers/hwmon/smm665.c b/drivers/hwmon/smm665.c
> > new file mode 100644
> > index 0000000..760afba
> > --- /dev/null
> > +++ b/drivers/hwmon/smm665.c
> > @@ -0,0 +1,748 @@
> > +/*
> > + * Driver for SMM665 Power Controller / Monitor
> > + *
> > + * 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; version 2 of the License.
> > + *
> > + * This driver should also work for SMM465, SMM764, and SMM766, but is untested
> > + * for those chips. Only monitoring functionality is implemented.
> > + *
> > + * Datasheets:
> > + * http://www.summitmicro.com/prod_select/summary/SMM665/SMM665B_2089_20.pdf
> > + * http://www.summitmicro.com/prod_select/summary/SMM766B/SMM766B_2122.pdf
> > + */
> > +
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/init.h>
> > +#include <linux/err.h>
> > +#include <linux/slab.h>
> > +#include <linux/i2c.h>
> > +#include <linux/hwmon.h>
> > +#include <linux/hwmon-sysfs.h>
> > +#include <linux/delay.h>
> > +
> > +/* Internal reference voltage (VREF, x 1000 */
> > +#define SMM665_VREF_ADC_X1000 1250
> > +
> > +/* module parameters */
> > +static int vref = SMM665_VREF_ADC_X1000;
> > +module_param(vref, int, 0);
> > +MODULE_PARM_DESC(vref, "Reference voltage in mV");
> > +
> > +enum chips { smm465, smm665, smm665c, smm764, smm766 };
> > +
> > +/*
> > + * ADC channel addresses
> > + */
> > +#define SMM665_MISC16_ADC_DATA_A 0x00
> > +#define SMM665_MISC16_ADC_DATA_B 0x01
> > +#define SMM665_MISC16_ADC_DATA_C 0x02
> > +#define SMM665_MISC16_ADC_DATA_D 0x03
> > +#define SMM665_MISC16_ADC_DATA_E 0x04
> > +#define SMM665_MISC16_ADC_DATA_F 0x05
> > +#define SMM665_MISC16_ADC_DATA_VDD 0x06
> > +#define SMM665_MISC16_ADC_DATA_12V 0x07
> > +#define SMM665_MISC16_ADC_DATA_INT_TEMP 0x08
> > +#define SMM665_MISC16_ADC_DATA_AIN1 0x09
> > +#define SMM665_MISC16_ADC_DATA_AIN2 0x0a
> > +
> > +/*
> > + * Command registers
> > + */
> > +#define SMM665_MISC8_CMD_STS 0x80
> > +#define SMM665_MISC8_STATUS1 0x81
> > +#define SMM665_MISC8_STATUSS2 0x82
> > +#define SMM665_MISC8_IO_POLARITY 0x83
> > +#define SMM665_MISC8_PUP_POLARITY 0x84
> > +#define SMM665_MISC8_ADOC_STATUS1 0x85
> > +#define SMM665_MISC8_ADOC_STATUS2 0x86
> > +#define SMM665_MISC8_WRITE_PROT 0x87
> > +#define SMM665_MISC8_STS_TRACK 0x88
> > +
> > +/*
> > + * Configuration registers and register groups
> > + */
> > +#define SMM665_ADOC_ENABLE 0x0d
> > +#define SMM665_LIMIT_BASE 0x80 /* First limit register */
> > +
> > +/*
> > + * Limit register bit masks
> > + */
> > +#define SMM665_TRIGGER_RST 0x8000
> > +#define SMM665_TRIGGER_HEALTHY 0x4000
> > +#define SMM665_TRIGGER_POWEROFF 0x2000
> > +#define SMM665_TRIGGER_SHUTDOWN 0x1000
> > +#define SMM665_ADC_MASK 0x03ff
> > +
> > +#define smm665_is_critical(lim) ((lim) & (SMM665_TRIGGER_RST \
> > + | SMM665_TRIGGER_POWEROFF \
> > + | SMM665_TRIGGER_SHUTDOWN))
> > +/*
> > + * Fault register bit definitions
> > + * Values are merged from status registers 1/2,
> > + * with status register 1 providing the upper 8 bits.
> > + */
> > +#define SMM665_FAULT_A 0x0001
> > +#define SMM665_FAULT_B 0x0002
> > +#define SMM665_FAULT_C 0x0004
> > +#define SMM665_FAULT_D 0x0008
> > +#define SMM665_FAULT_E 0x0010
> > +#define SMM665_FAULT_F 0x0020
> > +#define SMM665_FAULT_VDD 0x0040
> > +#define SMM665_FAULT_12V 0x0080
> > +#define SMM665_FAULT_TEMP 0x0100
> > +#define SMM665_FAULT_AIN1 0x0200
> > +#define SMM665_FAULT_AIN2 0x0400
> > +
> > +/*
> > + * I2C Register addresses
> > + *
> > + * The configuration register needs to be the configured base register.
> > + * The command/status register address is derived from it.
> > + */
> > +#define SMM665_REGMASK 0x78
> > +#define SMM665_CMDREG_BASE 0x48
> > +#define SMM665_CONFREG_BASE 0x50
> > +
> > +/*
> > + * Equations given by chip manufacturer to calculate voltage/temperature values
> > + * vref = Reference voltage on VREF_ADC pin (module parameter)
> > + * adc = 10bit ADC value read back from registers
> > + */
> > +
> > +/* Voltage A-F and VDD */
> > +#define SMM665_VMON_ADC_TO_VOLTS(adc) ((adc) * vref / 256)
> > +
> > +/* Voltage 12VIN */
> > +#define SMM665_12VIN_ADC_TO_VOLTS(adc) ((adc) * vref * 3 / 256)
> > +
> > +/* Voltage AIN1, AIN2 */
> > +#define SMM665_AIN_ADC_TO_VOLTS(adc) ((adc) * vref / 512)
> > +
> > +/* Temp Sensor */
> > +#define SMM665_TEMP_ADC_TO_CELSIUS(adc) ((adc) <= 511) ? \
> > + ((int)(adc) * 1000 / 4) : \
> > + (((int)(adc) - 0x400) * 1000 / 4)
> > +
> > +#define SMM665_NUM_ADC 11
> > +
> > +/*
> > + * Chip dependent ADC conversion time, in uS
> > + */
> > +#define SMM665_ADC_WAIT_SMM665 70
> > +#define SMM665_ADC_WAIT_SMM766 185
> > +
> > +struct smm665_data {
> > + enum chips type;
> > + int conversion_time; /* ADC conversion time */
> > + struct device *hwmon_dev;
> > + struct mutex update_lock;
> > + bool valid;
> > + unsigned long last_updated; /* in jiffies */
> > + u16 adc[SMM665_NUM_ADC]; /* adc values (raw) */
> > + u16 faults; /* fault status */
> > + /* The following values are in mV */
> > + int critical_min_limit[SMM665_NUM_ADC];
> > + int alarm_min_limit[SMM665_NUM_ADC];
> > + int critical_max_limit[SMM665_NUM_ADC];
> > + int alarm_max_limit[SMM665_NUM_ADC];
> > + struct i2c_client *cmdreg;
> > +};
> > +
> > +/*
> > + * smm665_read16()
> > + *
> > + * Read 16 bit value from <reg>, <reg+1>. Upper 8 bits are in <reg>.
> > + */
> > +static int smm665_read16(struct i2c_client *client, int reg)
> > +{
> > + int rv, val;
> > +
> > + rv = i2c_smbus_read_byte_data(client, reg);
> > + if (rv < 0)
> > + return rv;
> > + val = rv << 8;
> > + rv = i2c_smbus_read_byte_data(client, reg + 1);
> > + if (rv < 0)
> > + return rv;
> > + val |= rv;
> > + return val;
> > +}
> > +
> > +/*
> > + * Read adc value.
> > + */
> > +static int smm665_read_adc(struct smm665_data *data, int adc)
> > +{
> > + struct i2c_client *client = data->cmdreg;
> > + int rv;
> > + int radc;
> > +
> > + /*
> > + * Algorithm for reading ADC, per SMM665 datasheet
> > + *
> > + * {[S][addr][W][Ack]} {[offset][Ack]} {[S][addr][R][Nack]}
> > + * [wait conversion time]
> > + * {[S][addr][R][Ack]} {[datahi][Ack]} {[datalo][Ack][P]}
> > + *
> > + * To implement the first part of this exchange,
> > + * do a full read transaction and expect a failure/Nack.
> > + * This sets up the address pointer on the SMM665
> > + * and starts the ADC conversion.
> > + * Then do a two-byte read transaction.
> > + */
> > + rv = i2c_smbus_read_byte_data(client, adc << 3);
> > + if (rv != -ENXIO) {
> > + /*
> > + * We expect ENXIO to reflect NACK
> > + * (per Documentation/i2c/fault-codes).
> > + * Everything else is an error.
> > + */
> > + dev_dbg(&client->dev,
> > + "Unexpected return code %d when setting ADC index", rv);
> > + return (rv < 0) ? rv : -EIO;
> > + }
> > +
> > + udelay(data->conversion_time);
> > +
> > + /*
> > + * Now read two bytes.
> > + *
> > + * Neither i2c_smbus_read_byte() nor
> > + * i2c_smbus_read_block_data() worked here,
> > + * so use i2c_smbus_read_word_data() instead.
> > + * We could also try to use i2c_master_recv(),
> > + * but that is not always supported.
> > + */
> > + rv = i2c_smbus_read_word_data(client, 0);
> > + if (rv < 0) {
> > + dev_dbg(&client->dev, "Failed to read ADC value: error %d", rv);
> > + return -1;
> > + }
> > + /*
> > + * Validate/verify readback adc channel (in bit 11..14).
> > + * High byte is in lower 8 bit of rv, so only shift by 3.
> > + */
> > + radc = (rv >> 3) & 0x0f;
> > + if (radc != adc) {
> > + dev_dbg(&client->dev, "Unexpected RADC: Expected %d got %d",
> > + adc, radc);
> > + return -EIO;
> > + }
> > + /*
> > + * Chip replies with H/L, while SMBus expects L/H.
> > + * Thus, byte order is reversed, and we have to swap
> > + * the result.
> > + */
> > + rv = swab16(rv) & SMM665_ADC_MASK;
> > +
> > + return rv;
> > +}
> > +
> > +static struct smm665_data *smm665_update_device(struct device *dev)
> > +{
> > + struct i2c_client *client = to_i2c_client(dev);
> > + struct smm665_data *data = i2c_get_clientdata(client);
> > + struct smm665_data *ret = data;
> > +
> > + mutex_lock(&data->update_lock);
> > +
> > + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
> > + int i, val;
> > +
> > + /*
> > + * read status registers
> > + */
> > + val = smm665_read16(client, SMM665_MISC8_STATUS1);
> > + if (unlikely(val < 0)) {
> > + ret = ERR_PTR(val);
> > + goto abort;
> > + }
> > + data->faults = val;
> > +
> > + /* Read adc registers */
> > + for (i = 0; i < SMM665_NUM_ADC; i++) {
> > + val = smm665_read_adc(data, i);
> > + if (unlikely(val < 0)) {
> > + ret = ERR_PTR(val);
> > + goto abort;
> > + }
> > + data->adc[i] = val;
> > + }
> > + data->last_updated = jiffies;
> > + data->valid = 1;
> > + }
> > +abort:
> > + mutex_unlock(&data->update_lock);
> > + return ret;
> > +}
> > +
> > +/* Return converted value from given adc */
> > +static int smm665_convert(u16 adcval, int index)
> > +{
> > + int val = 0;
> > +
> > + switch (index) {
> > + case SMM665_MISC16_ADC_DATA_12V:
> > + val = SMM665_12VIN_ADC_TO_VOLTS(adcval & SMM665_ADC_MASK);
> > + break;
> > +
> > + case SMM665_MISC16_ADC_DATA_VDD:
> > + case SMM665_MISC16_ADC_DATA_A:
> > + case SMM665_MISC16_ADC_DATA_B:
> > + case SMM665_MISC16_ADC_DATA_C:
> > + case SMM665_MISC16_ADC_DATA_D:
> > + case SMM665_MISC16_ADC_DATA_E:
> > + case SMM665_MISC16_ADC_DATA_F:
> > + val = SMM665_VMON_ADC_TO_VOLTS(adcval & SMM665_ADC_MASK);
> > + break;
> > +
> > + case SMM665_MISC16_ADC_DATA_AIN1:
> > + case SMM665_MISC16_ADC_DATA_AIN2:
> > + val = SMM665_AIN_ADC_TO_VOLTS(adcval & SMM665_ADC_MASK);
> > + break;
> > +
> > + case SMM665_MISC16_ADC_DATA_INT_TEMP:
> > + val = SMM665_TEMP_ADC_TO_CELSIUS(adcval & SMM665_ADC_MASK);
> > + break;
> > +
> > + default:
> > + /* If we get here, the developer messed up */
> > + WARN_ON_ONCE(1);
> > + break;
> > + }
> > +
> > + return val;
> > +}
> > +
> > +static int smm665_get_min(struct device *dev, int index)
> > +{
> > + struct i2c_client *client = to_i2c_client(dev);
> > + struct smm665_data *data = i2c_get_clientdata(client);
> > +
> > + return data->alarm_min_limit[index];
> > +}
> > +
> > +static int smm665_get_max(struct device *dev, int index)
> > +{
> > + struct i2c_client *client = to_i2c_client(dev);
> > + struct smm665_data *data = i2c_get_clientdata(client);
> > +
> > + return data->alarm_max_limit[index];
> > +}
> > +
> > +static int smm665_get_lcrit(struct device *dev, int index)
> > +{
> > + struct i2c_client *client = to_i2c_client(dev);
> > + struct smm665_data *data = i2c_get_clientdata(client);
> > +
> > + return data->critical_min_limit[index];
> > +}
> > +
> > +static int smm665_get_crit(struct device *dev, int index)
> > +{
> > + struct i2c_client *client = to_i2c_client(dev);
> > + struct smm665_data *data = i2c_get_clientdata(client);
> > +
> > + return data->critical_max_limit[index];
> > +}
> > +
> > +static ssize_t smm665_show_fault(struct device *dev,
> > + struct device_attribute *da, char *buf)
> > +{
> > + struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
> > + struct smm665_data *data = smm665_update_device(dev);
> > + int val = 0;
> > +
> > + if (IS_ERR(data))
> > + return PTR_ERR(data);
> > +
> > + if (data->faults & (1 << attr->index))
> > + val = 1;
> > +
> > + return snprintf(buf, PAGE_SIZE, "%d\n", val);
> > +}
> > +
> > +static ssize_t smm665_show_input(struct device *dev,
> > + struct device_attribute *da, char *buf)
> > +{
> > + struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
> > + struct smm665_data *data = smm665_update_device(dev);
> > + int adc = attr->index;
> > + int val;
> > +
> > + if (IS_ERR(data))
> > + return PTR_ERR(data);
> > +
> > + val = smm665_convert(data->adc[adc], adc);
> > + return snprintf(buf, PAGE_SIZE, "%d\n", val);
> > +}
> > +
> > +#define SMM665_SHOW(what) \
> > + static ssize_t smm665_show_##what(struct device *dev, \
> > + struct device_attribute *da, char *buf) \
> > +{ \
> > + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); \
> > + const int val = smm665_get_##what(dev, attr->index); \
> > + return snprintf(buf, PAGE_SIZE, "%d\n", val); \
> > +}
> > +
> > +SMM665_SHOW(min);
> > +SMM665_SHOW(max);
> > +SMM665_SHOW(lcrit);
> > +SMM665_SHOW(crit);
> > +
> > +/* These macros are used below in constructing device attribute objects
> > + * for use with sysfs_create_group() to make a sysfs device file
> > + * for each register.
> > + */
> > +
> > +#define SMM665_ATTR(name, type, cmd_idx) \
> > + static SENSOR_DEVICE_ATTR(name##_##type, S_IRUGO, \
> > + smm665_show_##type, NULL, cmd_idx)
> > +
> > +/* Construct a sensor_device_attribute structure for each register */
> > +
> > +/* Input voltages */
> > +SMM665_ATTR(in1, input, SMM665_MISC16_ADC_DATA_12V);
> > +SMM665_ATTR(in2, input, SMM665_MISC16_ADC_DATA_VDD);
> > +SMM665_ATTR(in3, input, SMM665_MISC16_ADC_DATA_A);
> > +SMM665_ATTR(in4, input, SMM665_MISC16_ADC_DATA_B);
> > +SMM665_ATTR(in5, input, SMM665_MISC16_ADC_DATA_C);
> > +SMM665_ATTR(in6, input, SMM665_MISC16_ADC_DATA_D);
> > +SMM665_ATTR(in7, input, SMM665_MISC16_ADC_DATA_E);
> > +SMM665_ATTR(in8, input, SMM665_MISC16_ADC_DATA_F);
> > +SMM665_ATTR(in9, input, SMM665_MISC16_ADC_DATA_AIN1);
> > +SMM665_ATTR(in10, input, SMM665_MISC16_ADC_DATA_AIN2);
> > +
> > +/* Input voltages min */
> > +SMM665_ATTR(in1, min, SMM665_MISC16_ADC_DATA_12V);
> > +SMM665_ATTR(in2, min, SMM665_MISC16_ADC_DATA_VDD);
> > +SMM665_ATTR(in3, min, SMM665_MISC16_ADC_DATA_A);
> > +SMM665_ATTR(in4, min, SMM665_MISC16_ADC_DATA_B);
> > +SMM665_ATTR(in5, min, SMM665_MISC16_ADC_DATA_C);
> > +SMM665_ATTR(in6, min, SMM665_MISC16_ADC_DATA_D);
> > +SMM665_ATTR(in7, min, SMM665_MISC16_ADC_DATA_E);
> > +SMM665_ATTR(in8, min, SMM665_MISC16_ADC_DATA_F);
> > +SMM665_ATTR(in9, min, SMM665_MISC16_ADC_DATA_AIN1);
> > +SMM665_ATTR(in10, min, SMM665_MISC16_ADC_DATA_AIN2);
> > +
> > +/* Input voltages max */
> > +SMM665_ATTR(in1, max, SMM665_MISC16_ADC_DATA_12V);
> > +SMM665_ATTR(in2, max, SMM665_MISC16_ADC_DATA_VDD);
> > +SMM665_ATTR(in3, max, SMM665_MISC16_ADC_DATA_A);
> > +SMM665_ATTR(in4, max, SMM665_MISC16_ADC_DATA_B);
> > +SMM665_ATTR(in5, max, SMM665_MISC16_ADC_DATA_C);
> > +SMM665_ATTR(in6, max, SMM665_MISC16_ADC_DATA_D);
> > +SMM665_ATTR(in7, max, SMM665_MISC16_ADC_DATA_E);
> > +SMM665_ATTR(in8, max, SMM665_MISC16_ADC_DATA_F);
> > +SMM665_ATTR(in9, max, SMM665_MISC16_ADC_DATA_AIN1);
> > +SMM665_ATTR(in10, max, SMM665_MISC16_ADC_DATA_AIN2);
> > +
> > +/* Input voltages lcrit */
> > +SMM665_ATTR(in1, lcrit, SMM665_MISC16_ADC_DATA_12V);
> > +SMM665_ATTR(in2, lcrit, SMM665_MISC16_ADC_DATA_VDD);
> > +SMM665_ATTR(in3, lcrit, SMM665_MISC16_ADC_DATA_A);
> > +SMM665_ATTR(in4, lcrit, SMM665_MISC16_ADC_DATA_B);
> > +SMM665_ATTR(in5, lcrit, SMM665_MISC16_ADC_DATA_C);
> > +SMM665_ATTR(in6, lcrit, SMM665_MISC16_ADC_DATA_D);
> > +SMM665_ATTR(in7, lcrit, SMM665_MISC16_ADC_DATA_E);
> > +SMM665_ATTR(in8, lcrit, SMM665_MISC16_ADC_DATA_F);
> > +SMM665_ATTR(in9, lcrit, SMM665_MISC16_ADC_DATA_AIN1);
> > +SMM665_ATTR(in10, lcrit, SMM665_MISC16_ADC_DATA_AIN2);
> > +
> > +/* Input voltages crit */
> > +SMM665_ATTR(in1, crit, SMM665_MISC16_ADC_DATA_12V);
> > +SMM665_ATTR(in2, crit, SMM665_MISC16_ADC_DATA_VDD);
> > +SMM665_ATTR(in3, crit, SMM665_MISC16_ADC_DATA_A);
> > +SMM665_ATTR(in4, crit, SMM665_MISC16_ADC_DATA_B);
> > +SMM665_ATTR(in5, crit, SMM665_MISC16_ADC_DATA_C);
> > +SMM665_ATTR(in6, crit, SMM665_MISC16_ADC_DATA_D);
> > +SMM665_ATTR(in7, crit, SMM665_MISC16_ADC_DATA_E);
> > +SMM665_ATTR(in8, crit, SMM665_MISC16_ADC_DATA_F);
> > +SMM665_ATTR(in9, crit, SMM665_MISC16_ADC_DATA_AIN1);
> > +SMM665_ATTR(in10, crit, SMM665_MISC16_ADC_DATA_AIN2);
> > +
> > +/* Faults */
> > +SMM665_ATTR(in1, fault, SMM665_FAULT_12V);
> > +SMM665_ATTR(in2, fault, SMM665_FAULT_VDD);
> > +SMM665_ATTR(in3, fault, SMM665_FAULT_A);
> > +SMM665_ATTR(in4, fault, SMM665_FAULT_B);
> > +SMM665_ATTR(in5, fault, SMM665_FAULT_C);
> > +SMM665_ATTR(in6, fault, SMM665_FAULT_D);
> > +SMM665_ATTR(in7, fault, SMM665_FAULT_E);
> > +SMM665_ATTR(in8, fault, SMM665_FAULT_F);
> > +SMM665_ATTR(in9, fault, SMM665_FAULT_AIN1);
> > +SMM665_ATTR(in10, fault, SMM665_FAULT_AIN2);
> > +
> > +/* Temperature */
> > +SMM665_ATTR(temp1, input, SMM665_MISC16_ADC_DATA_INT_TEMP);
> > +SMM665_ATTR(temp1, min, SMM665_MISC16_ADC_DATA_INT_TEMP);
> > +SMM665_ATTR(temp1, max, SMM665_MISC16_ADC_DATA_INT_TEMP);
> > +SMM665_ATTR(temp1, lcrit, SMM665_MISC16_ADC_DATA_INT_TEMP);
> > +SMM665_ATTR(temp1, crit, SMM665_MISC16_ADC_DATA_INT_TEMP);
> > +SMM665_ATTR(temp1, fault, SMM665_FAULT_TEMP);
> > +
> > +/*
> > + * Finally, construct an array of pointers to members of the above objects,
> > + * as required for sysfs_create_group()
> > + */
> > +static struct attribute *smm665_attributes[] = {
> > + &sensor_dev_attr_in1_input.dev_attr.attr,
> > + &sensor_dev_attr_in1_min.dev_attr.attr,
> > + &sensor_dev_attr_in1_max.dev_attr.attr,
> > + &sensor_dev_attr_in1_lcrit.dev_attr.attr,
> > + &sensor_dev_attr_in1_crit.dev_attr.attr,
> > + &sensor_dev_attr_in1_fault.dev_attr.attr,
> > +
> > + &sensor_dev_attr_in2_input.dev_attr.attr,
> > + &sensor_dev_attr_in2_min.dev_attr.attr,
> > + &sensor_dev_attr_in2_max.dev_attr.attr,
> > + &sensor_dev_attr_in2_lcrit.dev_attr.attr,
> > + &sensor_dev_attr_in2_crit.dev_attr.attr,
> > + &sensor_dev_attr_in2_fault.dev_attr.attr,
> > +
> > + &sensor_dev_attr_in3_input.dev_attr.attr,
> > + &sensor_dev_attr_in3_min.dev_attr.attr,
> > + &sensor_dev_attr_in3_max.dev_attr.attr,
> > + &sensor_dev_attr_in3_lcrit.dev_attr.attr,
> > + &sensor_dev_attr_in3_crit.dev_attr.attr,
> > + &sensor_dev_attr_in3_fault.dev_attr.attr,
> > +
> > + &sensor_dev_attr_in4_input.dev_attr.attr,
> > + &sensor_dev_attr_in4_min.dev_attr.attr,
> > + &sensor_dev_attr_in4_max.dev_attr.attr,
> > + &sensor_dev_attr_in4_lcrit.dev_attr.attr,
> > + &sensor_dev_attr_in4_crit.dev_attr.attr,
> > + &sensor_dev_attr_in4_fault.dev_attr.attr,
> > +
> > + &sensor_dev_attr_in5_input.dev_attr.attr,
> > + &sensor_dev_attr_in5_min.dev_attr.attr,
> > + &sensor_dev_attr_in5_max.dev_attr.attr,
> > + &sensor_dev_attr_in5_lcrit.dev_attr.attr,
> > + &sensor_dev_attr_in5_crit.dev_attr.attr,
> > + &sensor_dev_attr_in5_fault.dev_attr.attr,
> > +
> > + &sensor_dev_attr_in6_input.dev_attr.attr,
> > + &sensor_dev_attr_in6_min.dev_attr.attr,
> > + &sensor_dev_attr_in6_max.dev_attr.attr,
> > + &sensor_dev_attr_in6_lcrit.dev_attr.attr,
> > + &sensor_dev_attr_in6_crit.dev_attr.attr,
> > + &sensor_dev_attr_in6_fault.dev_attr.attr,
> > +
> > + &sensor_dev_attr_in7_input.dev_attr.attr,
> > + &sensor_dev_attr_in7_min.dev_attr.attr,
> > + &sensor_dev_attr_in7_max.dev_attr.attr,
> > + &sensor_dev_attr_in7_lcrit.dev_attr.attr,
> > + &sensor_dev_attr_in7_crit.dev_attr.attr,
> > + &sensor_dev_attr_in7_fault.dev_attr.attr,
> > +
> > + &sensor_dev_attr_in8_input.dev_attr.attr,
> > + &sensor_dev_attr_in8_min.dev_attr.attr,
> > + &sensor_dev_attr_in8_max.dev_attr.attr,
> > + &sensor_dev_attr_in8_lcrit.dev_attr.attr,
> > + &sensor_dev_attr_in8_crit.dev_attr.attr,
> > + &sensor_dev_attr_in8_fault.dev_attr.attr,
> > +
> > + &sensor_dev_attr_in9_input.dev_attr.attr,
> > + &sensor_dev_attr_in9_min.dev_attr.attr,
> > + &sensor_dev_attr_in9_max.dev_attr.attr,
> > + &sensor_dev_attr_in9_lcrit.dev_attr.attr,
> > + &sensor_dev_attr_in9_crit.dev_attr.attr,
> > + &sensor_dev_attr_in9_fault.dev_attr.attr,
> > +
> > + &sensor_dev_attr_in10_input.dev_attr.attr,
> > + &sensor_dev_attr_in10_min.dev_attr.attr,
> > + &sensor_dev_attr_in10_max.dev_attr.attr,
> > + &sensor_dev_attr_in10_lcrit.dev_attr.attr,
> > + &sensor_dev_attr_in10_crit.dev_attr.attr,
> > + &sensor_dev_attr_in10_fault.dev_attr.attr,
> > +
> > + &sensor_dev_attr_temp1_input.dev_attr.attr,
> > + &sensor_dev_attr_temp1_min.dev_attr.attr,
> > + &sensor_dev_attr_temp1_max.dev_attr.attr,
> > + &sensor_dev_attr_temp1_lcrit.dev_attr.attr,
> > + &sensor_dev_attr_temp1_crit.dev_attr.attr,
> > + &sensor_dev_attr_temp1_fault.dev_attr.attr,
> > +
> > + NULL,
> > +};
> > +
> > +static const struct attribute_group smm665_group = {
> > + .attrs = smm665_attributes,
> > +};
> > +
> > +static int smm665_probe(struct i2c_client *client,
> > + const struct i2c_device_id *id)
> > +{
> > + struct i2c_adapter *adapter = client->adapter;
> > + struct smm665_data *data;
> > + int i, ret;
> > +
> > + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA
> > + | I2C_FUNC_SMBUS_WORD_DATA))
> > + return -ENODEV;
> > +
> > + if (i2c_smbus_read_byte_data(client, SMM665_ADOC_ENABLE) < 0)
> > + return -ENODEV;
> > +
> > + ret = -ENOMEM;
> > + data = kzalloc(sizeof(*data), GFP_KERNEL);
> > + if (!data)
> > + goto out_return;
> > +
> > + i2c_set_clientdata(client, data);
> > + mutex_init(&data->update_lock);
> > +
> > + data->type = id->driver_data;
> > + data->cmdreg = i2c_new_dummy(adapter, (client->addr & ~SMM665_REGMASK)
> > + | SMM665_CMDREG_BASE);
> > + if (!data->cmdreg)
> > + goto out_kfree;
> > +
> > + switch (data->type) {
> > + case smm465:
> > + case smm665:
> > + data->conversion_time = SMM665_ADC_WAIT_SMM665;
> > + break;
> > + case smm665c:
> > + case smm764:
> > + case smm766:
> > + data->conversion_time = SMM665_ADC_WAIT_SMM766;
> > + break;
> > + }
> > +
> > + ret = -ENODEV;
> > + if (i2c_smbus_read_byte_data(data->cmdreg, SMM665_MISC8_CMD_STS) < 0)
> > + goto out_unregister;
> > +
> > + /*
> > + * Read limits.
> > + *
> > + * Limit registers start with register SMM665_LIMIT_BASE.
> > + * Each channel uses 8 registers, providing four limit values
> > + * per channel. Each limit value requires two registers, with the
> > + * high byte in the first register and the low byte in the second
> > + * register. The first two limits are under limit values, followed
> > + * by two over limit values.
> > + *
> > + * Limit register order matches the ADC register order, so we use
> > + * ADC register defines throughout the code to index limit registers.
> > + *
> > + * We save the first retrieved value both as "critical" and "alarm"
> > + * value. The second value overwrites either the critical or the
> > + * alarm value, depending on its configuration. This ensures that both
> > + * critical and alarm values are initialized, even if both registers are
> > + * configured as critical or non-critical.
> > + *
> > + * Note: Critical values for voltage channels are saved, even though
> > + * this information is currently not used by the driver. This is mostly
> > + * for consistency, though it might eventually be useful if future APIs
> > + * support reporting "critical" voltage values.
> Comment is out of date. Looks like you just added the api ;)
>
> > + */
> > + for (i = 0; i < SMM665_NUM_ADC; i++) {
> > + int val;
> > +
> > + val = smm665_read16(client, SMM665_LIMIT_BASE + i * 8);
> > + if (unlikely(val < 0))
> > + goto out_unregister;
> > + data->critical_min_limit[i] = data->alarm_min_limit[i]
> > + = smm665_convert(val, i);
> > + val = smm665_read16(client, SMM665_LIMIT_BASE + i * 8 + 2);
> > + if (unlikely(val < 0))
> > + goto out_unregister;
> > + if (smm665_is_critical(val))
> > + data->critical_min_limit[i] = smm665_convert(val, i);
> > + else
> > + data->alarm_min_limit[i] = smm665_convert(val, i);
> > + val = smm665_read16(client, SMM665_LIMIT_BASE + i * 8 + 4);
> > + if (unlikely(val < 0))
> > + goto out_unregister;
> > + data->critical_max_limit[i] = data->alarm_max_limit[i]
> > + = smm665_convert(val, i);
> > + val = smm665_read16(client, SMM665_LIMIT_BASE + i * 8 + 6);
> > + if (unlikely(val < 0))
> > + goto out_unregister;
> > + if (smm665_is_critical(val))
> > + data->critical_max_limit[i] = smm665_convert(val, i);
> > + else
> > + data->alarm_max_limit[i] = smm665_convert(val, i);
> > + }
> > +
> > + /* Register sysfs hooks */
> > + ret = sysfs_create_group(&client->dev.kobj, &smm665_group);
> > + if (ret)
> > + goto out_unregister;
> > +
> > + data->hwmon_dev = hwmon_device_register(&client->dev);
> > + if (IS_ERR(data->hwmon_dev)) {
> > + ret = PTR_ERR(data->hwmon_dev);
> > + goto out_remove_group;
> > + }
> > +
> > + return 0;
> > +
> > +out_remove_group:
> > + sysfs_remove_group(&client->dev.kobj, &smm665_group);
> > +out_unregister:
> > + i2c_unregister_device(data->cmdreg);
> > +out_kfree:
> > + kfree(data);
> > +out_return:
> > + return ret;
> > +}
> > +
> > +static int smm665_remove(struct i2c_client *client)
> > +{
> > + struct smm665_data *data = i2c_get_clientdata(client);
> > +
> > + i2c_unregister_device(data->cmdreg);
> > + hwmon_device_unregister(data->hwmon_dev);
> > + sysfs_remove_group(&client->dev.kobj, &smm665_group);
> > +
> > + kfree(data);
> > +
> > + return 0;
> > +}
> > +
> > +static const struct i2c_device_id smm665_id[] = {
> > + {"smm465", smm465},
> > + {"smm665", smm665},
> > + {"smm665c", smm665c},
> > + {"smm764", smm764},
> > + {"smm766", smm766},
> > + {}
> > +};
> > +
> > +MODULE_DEVICE_TABLE(i2c, smm665_id);
> > +
> > +/* This is the driver that will be inserted */
> > +static struct i2c_driver smm665_driver = {
> > + .driver = {
> > + .name = "smm665",
> > + },
> > + .probe = smm665_probe,
> > + .remove = smm665_remove,
> > + .id_table = smm665_id,
> > +};
> > +
> > +static int __init smm665_init(void)
> > +{
> > + return i2c_add_driver(&smm665_driver);
> > +}
> > +
> > +static void __exit smm665_exit(void)
> > +{
> > + i2c_del_driver(&smm665_driver);
> > +}
> > +
> > +MODULE_AUTHOR("Guenter Roeck");
> > +MODULE_DESCRIPTION("SMM665 driver");
> > +MODULE_LICENSE("GPL");
> > +
> > +module_init(smm665_init);
> > +module_exit(smm665_exit);
>