All of lore.kernel.org
 help / color / mirror / Atom feed
* [lm-sensors] patch: asc7621 driver bug fixes
@ 2008-07-05 13:37 Ken Milmore
  2008-07-06  6:15 ` Hans de Goede
                   ` (10 more replies)
  0 siblings, 11 replies; 12+ messages in thread
From: Ken Milmore @ 2008-07-05 13:37 UTC (permalink / raw)
  To: lm-sensors

[-- Attachment #1: Type: text/plain, Size: 1946 bytes --]

George,

Here are some suggested bug fixes for the asc7621 driver source which 
you posted to lm_sensors on 29 May.
(http://lists.lm-sensors.org/pipermail/lm-sensors/2008-May/023257.html)

Attached patch #1 contains the following fixes; I think these are 
relatively uncontroversial:

show_in10() : fix incorrect scaling of the 2 LSB of input voltages.

store_in8() : fix calculation overflow which was corrupting voltage limits.

store_temp62() : avoid compiler warning.

asc7621_params : correct fan1-fan4 alarm bit shifts

asc7621_params : correct wrong address for temp3_smoothing_enable

asc7621_update_device() : fix typos which were causing incorrect
scanning of the low priority registers.

Now for the more controversial bit:  I'm rather concerned about the 
asc7621_register_priorities array, which appears to be a kind of 
reverse-index by register address of what is in asc7621_params.  It 
contains information which is constant, and known at compile time so it 
would be better not to have to go through all the trouble of building it 
up in asc7621_init_client().  You might want to consider moving this 
work into the module start-up code, rather than the per-client 
initialisation; it only needs to happen once.  A better solution might 
be to remove it altogether.  I've tried to do this in the attached patch 
#2, which uses hard-coded register lists.  The result isn't exactly 
pretty, but doing it in a cleaner way will require a lot of rework on 
the rest of the module.  Anyway, see what you think.

BTW, IMHO the alarms should be read as high priority registers, and I've 
reflected this in the patch.

While I'm on, I'd like to say many thanks to you for taking the time and 
trouble to write this driver!  Given that Andigilog have provided such 
excellent documentation for these chips, it is a shame that there is 
still no support for them in the kernel and I hope that will soon be 
rectified.

Best wishes,

Ken.


[-- Attachment #2: asc7621_patch1.diff --]
[-- Type: text/plain, Size: 4367 bytes --]

--- asc7621.c.orig	2008-07-04 20:33:09.000000000 +0100
+++ asc7621.c	2008-07-05 13:24:12.000000000 +0100
@@ -278,19 +278,16 @@
 static ssize_t show_in10(struct device *dev, struct device_attribute *attr,
                         char *buf)
 {
-       SETUP_SHOW_data_param(dev, attr);
+       u8 nr;
+       unsigned int regval;
 
-       u8 nr = sda->index;
-       u16 regval = (data->reg[param->msb[0]] * asc7621_in_scaling[nr]) / 256;
+       SETUP_SHOW_data_param(dev, attr);
 
-       /* The LSB value is a 2-bit scaling of the MSB's LSbit value.
-        * I.E.  If the maximim voltage for this input is 6640 millivolts then
-        * a MSB register value of 0 = 0mv and 255 = 6640mv.
-        * A 1 step change therefore represents 25.9mv (6640 / 256).
-        * The extra 2-bits therefore represent increments of 6.48mv.
-        */
-       regval += ((asc7621_in_scaling[nr] / 256) / 4) *
-           (data->reg[param->lsb[0]] >> 6);
+       nr = sda->index;
+       /* The LSB value is a 2-bit scaling of the MSB's LSbit value. */
+       regval = (data->reg[param->msb[0]] << 2) +
+                (data->reg[param->lsb[0]] >> 6);
+       regval = regval * asc7621_in_scaling[nr] / 256 / 4;
 
        return sprintf(buf, "%u\n", regval);
 }
@@ -311,12 +308,15 @@
 static ssize_t store_in8(struct device *dev, struct device_attribute *attr,
                         const char *buf, size_t count)
 {
+       u8 nr;
+       unsigned int reqval;
+
        SETUP_STORE_data_param(dev, attr);
 
-       u8 nr = sda->index;
-       u8 reqval = simple_strtoul(buf, NULL, 10);
-       reqval =
-           SENSORS_LIMIT(((reqval * 256) / asc7621_in_scaling[nr]), 0, 255);
+       nr = sda->index;
+       reqval = simple_strtoul(buf, NULL, 10);
+       reqval = reqval * 256 / asc7621_in_scaling[nr];
+       reqval = SENSORS_LIMIT(reqval, 0, 255);
 
        mutex_lock(&data->update_lock);
        data->reg[param->msb[0]] = reqval;
@@ -385,12 +385,14 @@
                            struct device_attribute *attr, const char *buf,
                            size_t count)
 {
+       s32 reqval;
+       s32 i, f;
+       s8 temp;
+
        SETUP_STORE_data_param(dev, attr);
 
-       s32 reqval = simple_strtol(buf, NULL, 10);
+       reqval = simple_strtol(buf, NULL, 10);
        reqval = SENSORS_LIMIT(reqval, -32000, 31750);
-       s32 i, f;
-       s8 temp;
        i = reqval / 1000;
        f = reqval - (i * 1000);
        temp = i << 2;
@@ -853,10 +855,10 @@
        PWRITE(fan3_min, 2, PRI_LOW, 0x59, 0x58, 0, 0, fan16),
        PWRITE(fan4_min, 3, PRI_LOW, 0x5b, 0x5a, 0, 0, fan16),
 
-       PREAD(fan1_alarm, 0, PRI_LOW, 0x42, 0, 0x01, 0, bitmask),
-       PREAD(fan2_alarm, 1, PRI_LOW, 0x42, 0, 0x01, 1, bitmask),
-       PREAD(fan3_alarm, 2, PRI_LOW, 0x42, 0, 0x01, 2, bitmask),
-       PREAD(fan4_alarm, 3, PRI_LOW, 0x42, 0, 0x01, 3, bitmask),
+       PREAD(fan1_alarm, 0, PRI_LOW, 0x42, 0, 0x01, 2, bitmask),
+       PREAD(fan2_alarm, 1, PRI_LOW, 0x42, 0, 0x01, 3, bitmask),
+       PREAD(fan3_alarm, 2, PRI_LOW, 0x42, 0, 0x01, 4, bitmask),
+       PREAD(fan4_alarm, 3, PRI_LOW, 0x42, 0, 0x01, 5, bitmask),
 
        PREAD(temp1_input, 0, PRI_HIGH, 0x25, 0x10, 0, 0, temp10),
        PREAD(temp2_input, 1, PRI_HIGH, 0x26, 0x15, 0, 0, temp10),
@@ -911,7 +913,7 @@
 
        PWRITE(temp1_smoothing_enable, 0, PRI_LOW, 0x62, 0, 0x01, 3, bitmask),
        PWRITE(temp2_smoothing_enable, 1, PRI_LOW, 0x63, 0, 0x01, 7, bitmask),
-       PWRITE(temp3_smoothing_enable, 2, PRI_LOW, 0x64, 0, 0x01, 3, bitmask),
+       PWRITE(temp3_smoothing_enable, 2, PRI_LOW, 0x63, 0, 0x01, 3, bitmask),
        PWRITE(temp4_smoothing_enable, 3, PRI_LOW, 0x3c, 0, 0x01, 3, bitmask),
 
        PWRITE(temp1_smoothing_time, 0, PRI_LOW, 0x62, 0, 0x07, 0, temp_st),
@@ -1047,9 +1049,9 @@
        /* Read all the low priority registers. */
 
        if (!data->valid ||
-           time_after(jiffies, data->last_high_reading + INTERVAL_LOW)) {
+           time_after(jiffies, data->last_low_reading + INTERVAL_LOW)) {
 
-               for (i = 0; i < ARRAY_SIZE(asc7621_params); i++) {
+               for (i = 0; i < ARRAY_SIZE(asc7621_register_priorities); i++) {
                        if (asc7621_register_priorities[i] == PRI_LOW) {
                                data->reg[i] =
                                    i2c_smbus_read_byte_data(client, i) & 0xff;

[-- Attachment #3: asc7621_patch2.diff --]
[-- Type: text/plain, Size: 25195 bytes --]

--- asc7621_patch1.c	2008-07-05 13:18:01.000000000 +0100
+++ asc7621.c	2008-07-05 13:28:29.000000000 +0100
@@ -121,7 +121,6 @@
 */
 struct asc7621_param {
        struct sensor_device_attribute sda;
-       u8 priority;
        u8 msb[3];
        u8 lsb[3];
        u8 mask[3];
@@ -130,12 +129,6 @@
        char *label;
 };
 
-/*
- * This is the map that ultimately indicates whether we'll be
- * retrieving a register value or not, and at what frequency.
- */
-static u8 asc7621_register_priorities[255];
-
 static struct asc7621_data *asc7621_update_device(struct device *dev);
 
 #define read_byte(reg) (i2c_smbus_read_byte_data(client, reg) & 0xff)
@@ -783,38 +776,38 @@
  */
 #define VAA(args...) {args}
 
-#define PREAD(name, n, pri, rm, rl, m, s, r) \
+#define PREAD(name, n, rm, rl, m, s, r) \
        {.sda = SENSOR_ATTR(name, S_IRUGO, show_##r, NULL, n), \
-         .priority = pri,.msb[0] = rm, .lsb[0] = rl, .mask[0] = m, \
+         .msb[0] = rm, .lsb[0] = rl, .mask[0] = m, \
          .shift[0] = s,}
 
-#define PWRITE(name, n, pri, rm, rl, m, s, r) \
+#define PWRITE(name, n, rm, rl, m, s, r) \
        {.sda = SENSOR_ATTR(name, S_IRUGO | S_IWUSR, show_##r, store_##r, n), \
-         .priority = pri,.msb[0] = rm, .lsb[0] = rl, .mask[0] = m, \
+         .msb[0] = rm, .lsb[0] = rl, .mask[0] = m, \
          .shift[0] = s,}
 
 /*
  * PWRITEM assumes that the initializers for the .msb, .lsb, .mask and .shift
  * were created using the VAA macro.
  */
-#define PWRITEM(name, n, pri, rm, rl, m, s, r) \
+#define PWRITEM(name, n, rm, rl, m, s, r) \
        {.sda = SENSOR_ATTR(name, S_IRUGO | S_IWUSR, show_##r, store_##r, n), \
-         .priority = pri,.msb = rm, .lsb = rl, .mask = m, .shift = s,}
+         .msb = rm, .lsb = rl, .mask = m, .shift = s,}
 
 #define PCONST(name, n, v) \
        {.sda = SENSOR_ATTR(name, S_IRUGO, show_const, NULL, n), \
-         .priority = PRI_LOW, .value = v,}
+         .value = v,}
 
 #define PLABEL(name, n, v) \
        {.sda = SENSOR_ATTR(name, S_IRUGO, show_label, NULL, n), \
-         .priority = PRI_LOW, .label = v,}
+         .label = v,}
 
 static struct asc7621_param asc7621_params[] = {
-       PREAD(in0_input, 0, PRI_HIGH, 0x20, 0x13, 0, 0, in10),
-       PREAD(in1_input, 1, PRI_HIGH, 0x21, 0x18, 0, 0, in10),
-       PREAD(in2_input, 2, PRI_HIGH, 0x22, 0x11, 0, 0, in10),
-       PREAD(in3_input, 3, PRI_HIGH, 0x23, 0x12, 0, 0, in10),
-       PREAD(in4_input, 4, PRI_HIGH, 0x24, 0x14, 0, 0, in10),
+       PREAD(in0_input, 0, 0x20, 0x13, 0, 0, in10),
+       PREAD(in1_input, 1, 0x21, 0x18, 0, 0, in10),
+       PREAD(in2_input, 2, 0x22, 0x11, 0, 0, in10),
+       PREAD(in3_input, 3, 0x23, 0x12, 0, 0, in10),
+       PREAD(in4_input, 4, 0x24, 0x14, 0, 0, in10),
 
        PLABEL(in0_label, 0, "in0"),
        PLABEL(in1_label, 1, "in1"),
@@ -822,52 +815,52 @@
        PLABEL(in3_label, 3, "in3"),
        PLABEL(in4_label, 4, "in4"),
 
-       PWRITE(in0_min, 0, PRI_LOW, 0x44, 0, 0, 0, in8),
-       PWRITE(in1_min, 1, PRI_LOW, 0x46, 0, 0, 0, in8),
-       PWRITE(in2_min, 2, PRI_LOW, 0x48, 0, 0, 0, in8),
-       PWRITE(in3_min, 3, PRI_LOW, 0x4a, 0, 0, 0, in8),
-       PWRITE(in4_min, 4, PRI_LOW, 0x4c, 0, 0, 0, in8),
-
-       PWRITE(in0_max, 0, PRI_LOW, 0x45, 0, 0, 0, in8),
-       PWRITE(in1_max, 1, PRI_LOW, 0x47, 0, 0, 0, in8),
-       PWRITE(in2_max, 2, PRI_LOW, 0x49, 0, 0, 0, in8),
-       PWRITE(in3_max, 3, PRI_LOW, 0x4b, 0, 0, 0, in8),
-       PWRITE(in4_max, 4, PRI_LOW, 0x4d, 0, 0, 0, in8),
-
-       PREAD(in0_alarm, 0, PRI_LOW, 0x41, 0, 0x01, 0, bitmask),
-       PREAD(in1_alarm, 1, PRI_LOW, 0x41, 0, 0x01, 1, bitmask),
-       PREAD(in2_alarm, 2, PRI_LOW, 0x41, 0, 0x01, 2, bitmask),
-       PREAD(in3_alarm, 3, PRI_LOW, 0x41, 0, 0x01, 3, bitmask),
-       PREAD(in4_alarm, 4, PRI_LOW, 0x42, 0, 0x01, 0, bitmask),
-
-       PREAD(fan1_input, 0, PRI_HIGH, 0x29, 0x28, 0, 0, fan16),
-       PREAD(fan2_input, 1, PRI_HIGH, 0x2b, 0x2a, 0, 0, fan16),
-       PREAD(fan3_input, 2, PRI_HIGH, 0x2d, 0x2c, 0, 0, fan16),
-       PREAD(fan4_input, 3, PRI_HIGH, 0x2f, 0x2e, 0, 0, fan16),
+       PWRITE(in0_min, 0, 0x44, 0, 0, 0, in8),
+       PWRITE(in1_min, 1, 0x46, 0, 0, 0, in8),
+       PWRITE(in2_min, 2, 0x48, 0, 0, 0, in8),
+       PWRITE(in3_min, 3, 0x4a, 0, 0, 0, in8),
+       PWRITE(in4_min, 4, 0x4c, 0, 0, 0, in8),
+
+       PWRITE(in0_max, 0, 0x45, 0, 0, 0, in8),
+       PWRITE(in1_max, 1, 0x47, 0, 0, 0, in8),
+       PWRITE(in2_max, 2, 0x49, 0, 0, 0, in8),
+       PWRITE(in3_max, 3, 0x4b, 0, 0, 0, in8),
+       PWRITE(in4_max, 4, 0x4d, 0, 0, 0, in8),
+
+       PREAD(in0_alarm, 0, 0x41, 0, 0x01, 0, bitmask),
+       PREAD(in1_alarm, 1, 0x41, 0, 0x01, 1, bitmask),
+       PREAD(in2_alarm, 2, 0x41, 0, 0x01, 2, bitmask),
+       PREAD(in3_alarm, 3, 0x41, 0, 0x01, 3, bitmask),
+       PREAD(in4_alarm, 4, 0x42, 0, 0x01, 0, bitmask),
+
+       PREAD(fan1_input, 0, 0x29, 0x28, 0, 0, fan16),
+       PREAD(fan2_input, 1, 0x2b, 0x2a, 0, 0, fan16),
+       PREAD(fan3_input, 2, 0x2d, 0x2c, 0, 0, fan16),
+       PREAD(fan4_input, 3, 0x2f, 0x2e, 0, 0, fan16),
 
        PLABEL(fan1_label, 0, "fan1"),
        PLABEL(fan2_label, 1, "fan2"),
        PLABEL(fan3_label, 2, "fan3"),
        PLABEL(fan4_label, 3, "fan4"),
 
-       PWRITE(fan1_min, 0, PRI_LOW, 0x55, 0x54, 0, 0, fan16),
-       PWRITE(fan2_min, 1, PRI_LOW, 0x57, 0x56, 0, 0, fan16),
-       PWRITE(fan3_min, 2, PRI_LOW, 0x59, 0x58, 0, 0, fan16),
-       PWRITE(fan4_min, 3, PRI_LOW, 0x5b, 0x5a, 0, 0, fan16),
-
-       PREAD(fan1_alarm, 0, PRI_LOW, 0x42, 0, 0x01, 2, bitmask),
-       PREAD(fan2_alarm, 1, PRI_LOW, 0x42, 0, 0x01, 3, bitmask),
-       PREAD(fan3_alarm, 2, PRI_LOW, 0x42, 0, 0x01, 4, bitmask),
-       PREAD(fan4_alarm, 3, PRI_LOW, 0x42, 0, 0x01, 5, bitmask),
-
-       PREAD(temp1_input, 0, PRI_HIGH, 0x25, 0x10, 0, 0, temp10),
-       PREAD(temp2_input, 1, PRI_HIGH, 0x26, 0x15, 0, 0, temp10),
-       PREAD(temp3_input, 2, PRI_HIGH, 0x27, 0x16, 0, 0, temp10),
-       PREAD(temp4_input, 3, PRI_HIGH, 0x33, 0x17, 0, 0, temp10),
-       PREAD(temp5_input, 4, PRI_HIGH, 0xf7, 0xf6, 0, 0, temp10),
-       PREAD(temp6_input, 5, PRI_HIGH, 0xf9, 0xf8, 0, 0, temp10),
-       PREAD(temp7_input, 6, PRI_HIGH, 0xfb, 0xfa, 0, 0, temp10),
-       PREAD(temp8_input, 7, PRI_HIGH, 0xfd, 0xfc, 0, 0, temp10),
+       PWRITE(fan1_min, 0, 0x55, 0x54, 0, 0, fan16),
+       PWRITE(fan2_min, 1, 0x57, 0x56, 0, 0, fan16),
+       PWRITE(fan3_min, 2, 0x59, 0x58, 0, 0, fan16),
+       PWRITE(fan4_min, 3, 0x5b, 0x5a, 0, 0, fan16),
+
+       PREAD(fan1_alarm, 0, 0x42, 0, 0x01, 2, bitmask),
+       PREAD(fan2_alarm, 1, 0x42, 0, 0x01, 3, bitmask),
+       PREAD(fan3_alarm, 2, 0x42, 0, 0x01, 4, bitmask),
+       PREAD(fan4_alarm, 3, 0x42, 0, 0x01, 5, bitmask),
+
+       PREAD(temp1_input, 0, 0x25, 0x10, 0, 0, temp10),
+       PREAD(temp2_input, 1, 0x26, 0x15, 0, 0, temp10),
+       PREAD(temp3_input, 2, 0x27, 0x16, 0, 0, temp10),
+       PREAD(temp4_input, 3, 0x33, 0x17, 0, 0, temp10),
+       PREAD(temp5_input, 4, 0xf7, 0xf6, 0, 0, temp10),
+       PREAD(temp6_input, 5, 0xf9, 0xf8, 0, 0, temp10),
+       PREAD(temp7_input, 6, 0xfb, 0xfa, 0, 0, temp10),
+       PREAD(temp8_input, 7, 0xfd, 0xfc, 0, 0, temp10),
 
        PCONST(temp1_type, 0, 1),
        PCONST(temp2_type, 1, 3),
@@ -887,134 +880,176 @@
        PLABEL(temp7_label, 6, "peci 3"),
        PLABEL(temp8_label, 7, "peci 4"),
 
-       PWRITE(temp1_min, 0, PRI_LOW, 0x4e, 0, 0, 0, temp8),
-       PWRITE(temp2_min, 1, PRI_LOW, 0x50, 0, 0, 0, temp8),
-       PWRITE(temp3_min, 2, PRI_LOW, 0x52, 0, 0, 0, temp8),
-       PWRITE(temp4_min, 3, PRI_LOW, 0x34, 0, 0, 0, temp8),
-
-       PWRITE(temp1_max, 0, PRI_LOW, 0x4f, 0, 0, 0, temp8),
-       PWRITE(temp2_max, 1, PRI_LOW, 0x51, 0, 0, 0, temp8),
-       PWRITE(temp3_max, 2, PRI_LOW, 0x53, 0, 0, 0, temp8),
-       PWRITE(temp4_max, 3, PRI_LOW, 0x35, 0, 0, 0, temp8),
-
-       PREAD(temp1_alarm, 0, PRI_LOW, 0x41, 0, 0x01, 4, bitmask),
-       PREAD(temp2_alarm, 1, PRI_LOW, 0x41, 0, 0x01, 5, bitmask),
-       PREAD(temp3_alarm, 2, PRI_LOW, 0x41, 0, 0x01, 6, bitmask),
-       PREAD(temp4_alarm, 3, PRI_LOW, 0x43, 0, 0x01, 0, bitmask),
-
-       PWRITE(temp1_source, 0, PRI_LOW, 0x02, 0, 0x07, 4, bitmask),
-       PWRITE(temp2_source, 1, PRI_LOW, 0x02, 0, 0x07, 0, bitmask),
-       PWRITE(temp3_source, 2, PRI_LOW, 0x03, 0, 0x07, 4, bitmask),
-       PWRITE(temp4_source, 3, PRI_LOW, 0x03, 0, 0x07, 0, bitmask),
+       PWRITE(temp1_min, 0, 0x4e, 0, 0, 0, temp8),
+       PWRITE(temp2_min, 1, 0x50, 0, 0, 0, temp8),
+       PWRITE(temp3_min, 2, 0x52, 0, 0, 0, temp8),
+       PWRITE(temp4_min, 3, 0x34, 0, 0, 0, temp8),
+
+       PWRITE(temp1_max, 0, 0x4f, 0, 0, 0, temp8),
+       PWRITE(temp2_max, 1, 0x51, 0, 0, 0, temp8),
+       PWRITE(temp3_max, 2, 0x53, 0, 0, 0, temp8),
+       PWRITE(temp4_max, 3, 0x35, 0, 0, 0, temp8),
+
+       PREAD(temp1_alarm, 0, 0x41, 0, 0x01, 4, bitmask),
+       PREAD(temp2_alarm, 1, 0x41, 0, 0x01, 5, bitmask),
+       PREAD(temp3_alarm, 2, 0x41, 0, 0x01, 6, bitmask),
+       PREAD(temp4_alarm, 3, 0x43, 0, 0x01, 0, bitmask),
+
+       PWRITE(temp1_source, 0, 0x02, 0, 0x07, 4, bitmask),
+       PWRITE(temp2_source, 1, 0x02, 0, 0x07, 0, bitmask),
+       PWRITE(temp3_source, 2, 0x03, 0, 0x07, 4, bitmask),
+       PWRITE(temp4_source, 3, 0x03, 0, 0x07, 0, bitmask),
        PCONST(temp5_source, 4, 4),
        PCONST(temp6_source, 5, 5),
        PCONST(temp7_source, 6, 6),
        PCONST(temp8_source, 7, 7),
 
-       PWRITE(temp1_smoothing_enable, 0, PRI_LOW, 0x62, 0, 0x01, 3, bitmask),
-       PWRITE(temp2_smoothing_enable, 1, PRI_LOW, 0x63, 0, 0x01, 7, bitmask),
-       PWRITE(temp3_smoothing_enable, 2, PRI_LOW, 0x63, 0, 0x01, 3, bitmask),
-       PWRITE(temp4_smoothing_enable, 3, PRI_LOW, 0x3c, 0, 0x01, 3, bitmask),
-
-       PWRITE(temp1_smoothing_time, 0, PRI_LOW, 0x62, 0, 0x07, 0, temp_st),
-       PWRITE(temp2_smoothing_time, 1, PRI_LOW, 0x63, 0, 0x07, 4, temp_st),
-       PWRITE(temp3_smoothing_time, 2, PRI_LOW, 0x63, 0, 0x07, 0, temp_st),
-       PWRITE(temp4_smoothing_time, 3, PRI_LOW, 0x3c, 0, 0x07, 0, temp_st),
+       PWRITE(temp1_smoothing_enable, 0, 0x62, 0, 0x01, 3, bitmask),
+       PWRITE(temp2_smoothing_enable, 1, 0x63, 0, 0x01, 7, bitmask),
+       PWRITE(temp3_smoothing_enable, 2, 0x63, 0, 0x01, 3, bitmask),
+       PWRITE(temp4_smoothing_enable, 3, 0x3c, 0, 0x01, 3, bitmask),
+
+       PWRITE(temp1_smoothing_time, 0, 0x62, 0, 0x07, 0, temp_st),
+       PWRITE(temp2_smoothing_time, 1, 0x63, 0, 0x07, 4, temp_st),
+       PWRITE(temp3_smoothing_time, 2, 0x63, 0, 0x07, 0, temp_st),
+       PWRITE(temp4_smoothing_time, 3, 0x3c, 0, 0x07, 0, temp_st),
 
-       PWRITE(temp1_auto_point1_temp_hyst, 0, PRI_LOW, 0x6d, 0, 0x0f, 4,
+       PWRITE(temp1_auto_point1_temp_hyst, 0, 0x6d, 0, 0x0f, 4,
               bitmask),
-       PWRITE(temp2_auto_point1_temp_hyst, 1, PRI_LOW, 0x6d, 0, 0x0f, 0,
+       PWRITE(temp2_auto_point1_temp_hyst, 1, 0x6d, 0, 0x0f, 0,
               bitmask),
-       PWRITE(temp3_auto_point1_temp_hyst, 2, PRI_LOW, 0x6e, 0, 0x0f, 4,
+       PWRITE(temp3_auto_point1_temp_hyst, 2, 0x6e, 0, 0x0f, 4,
               bitmask),
-       PWRITE(temp4_auto_point1_temp_hyst, 3, PRI_LOW, 0x6e, 0, 0x0f, 0,
+       PWRITE(temp4_auto_point1_temp_hyst, 3, 0x6e, 0, 0x0f, 0,
               bitmask),
 
-       PREAD(temp1_auto_point2_temp_hyst, 0, PRI_LOW, 0x6d, 0, 0x0f, 4,
+       PREAD(temp1_auto_point2_temp_hyst, 0, 0x6d, 0, 0x0f, 4,
              bitmask),
-       PREAD(temp2_auto_point2_temp_hyst, 1, PRI_LOW, 0x6d, 0, 0x0f, 0,
+       PREAD(temp2_auto_point2_temp_hyst, 1, 0x6d, 0, 0x0f, 0,
              bitmask),
-       PREAD(temp3_auto_point2_temp_hyst, 2, PRI_LOW, 0x6e, 0, 0x0f, 4,
+       PREAD(temp3_auto_point2_temp_hyst, 2, 0x6e, 0, 0x0f, 4,
              bitmask),
-       PREAD(temp4_auto_point2_temp_hyst, 3, PRI_LOW, 0x6e, 0, 0x0f, 0,
+       PREAD(temp4_auto_point2_temp_hyst, 3, 0x6e, 0, 0x0f, 0,
              bitmask),
 
-       PWRITE(temp1_auto_point1_temp, 0, PRI_LOW, 0x67, 0, 0, 0, temp8),
-       PWRITE(temp2_auto_point1_temp, 1, PRI_LOW, 0x68, 0, 0, 0, temp8),
-       PWRITE(temp3_auto_point1_temp, 2, PRI_LOW, 0x69, 0, 0, 0, temp8),
-       PWRITE(temp4_auto_point1_temp, 3, PRI_LOW, 0x3b, 0, 0, 0, temp8),
+       PWRITE(temp1_auto_point1_temp, 0, 0x67, 0, 0, 0, temp8),
+       PWRITE(temp2_auto_point1_temp, 1, 0x68, 0, 0, 0, temp8),
+       PWRITE(temp3_auto_point1_temp, 2, 0x69, 0, 0, 0, temp8),
+       PWRITE(temp4_auto_point1_temp, 3, 0x3b, 0, 0, 0, temp8),
 
-       PWRITEM(temp1_auto_point2_temp, 0, PRI_LOW, VAA(0x5f, 0x67), VAA(0),
+       PWRITEM(temp1_auto_point2_temp, 0, VAA(0x5f, 0x67), VAA(0),
                VAA(0x0f), VAA(4), ap2_temp),
-       PWRITEM(temp2_auto_point2_temp, 1, PRI_LOW, VAA(0x60, 0x68), VAA(0),
+       PWRITEM(temp2_auto_point2_temp, 1, VAA(0x60, 0x68), VAA(0),
                VAA(0x0f), VAA(4), ap2_temp),
-       PWRITEM(temp3_auto_point2_temp, 2, PRI_LOW, VAA(0x61, 0x69), VAA(0),
+       PWRITEM(temp3_auto_point2_temp, 2, VAA(0x61, 0x69), VAA(0),
                VAA(0x0f), VAA(4), ap2_temp),
-       PWRITEM(temp4_auto_point2_temp, 3, PRI_LOW, VAA(0x3c, 0x3b), VAA(0),
+       PWRITEM(temp4_auto_point2_temp, 3, VAA(0x3c, 0x3b), VAA(0),
                VAA(0x0f), VAA(4), ap2_temp),
 
-       PWRITE(temp1_crit, 0, PRI_LOW, 0x6a, 0, 0, 0, temp8),
-       PWRITE(temp2_crit, 1, PRI_LOW, 0x6b, 0, 0, 0, temp8),
-       PWRITE(temp3_crit, 2, PRI_LOW, 0x6c, 0, 0, 0, temp8),
-       PWRITE(temp4_crit, 3, PRI_LOW, 0x3d, 0, 0, 0, temp8),
-
-       PWRITE(temp5_enable, 4, PRI_LOW, 0x0e, 0, 0x01, 0, bitmask),
-       PWRITE(temp6_enable, 5, PRI_LOW, 0x0e, 0, 0x01, 1, bitmask),
-       PWRITE(temp7_enable, 6, PRI_LOW, 0x0e, 0, 0x01, 2, bitmask),
-       PWRITE(temp8_enable, 7, PRI_LOW, 0x0e, 0, 0x01, 3, bitmask),
-
-       PWRITE(remote1_offset, 0, PRI_LOW, 0x1c, 0, 0, 0, temp62),
-       PWRITE(remote2_offset, 1, PRI_LOW, 0x1d, 0, 0, 0, temp62),
-
-       PWRITE(pwm1, 0, PRI_HIGH, 0x30, 0, 0, 0, u8),
-       PWRITE(pwm2, 1, PRI_HIGH, 0x31, 0, 0, 0, u8),
-       PWRITE(pwm3, 2, PRI_HIGH, 0x32, 0, 0, 0, u8),
-
-       PWRITE(pwm1_invert, 0, PRI_LOW, 0x5c, 0, 0x01, 4, bitmask),
-       PWRITE(pwm2_invert, 1, PRI_LOW, 0x5d, 0, 0x01, 4, bitmask),
-       PWRITE(pwm3_invert, 2, PRI_LOW, 0x5e, 0, 0x01, 4, bitmask),
+       PWRITE(temp1_crit, 0, 0x6a, 0, 0, 0, temp8),
+       PWRITE(temp2_crit, 1, 0x6b, 0, 0, 0, temp8),
+       PWRITE(temp3_crit, 2, 0x6c, 0, 0, 0, temp8),
+       PWRITE(temp4_crit, 3, 0x3d, 0, 0, 0, temp8),
+
+       PWRITE(temp5_enable, 4, 0x0e, 0, 0x01, 0, bitmask),
+       PWRITE(temp6_enable, 5, 0x0e, 0, 0x01, 1, bitmask),
+       PWRITE(temp7_enable, 6, 0x0e, 0, 0x01, 2, bitmask),
+       PWRITE(temp8_enable, 7, 0x0e, 0, 0x01, 3, bitmask),
+
+       PWRITE(remote1_offset, 0, 0x1c, 0, 0, 0, temp62),
+       PWRITE(remote2_offset, 1, 0x1d, 0, 0, 0, temp62),
+
+       PWRITE(pwm1, 0, 0x30, 0, 0, 0, u8),
+       PWRITE(pwm2, 1, 0x31, 0, 0, 0, u8),
+       PWRITE(pwm3, 2, 0x32, 0, 0, 0, u8),
+
+       PWRITE(pwm1_invert, 0, 0x5c, 0, 0x01, 4, bitmask),
+       PWRITE(pwm2_invert, 1, 0x5d, 0, 0x01, 4, bitmask),
+       PWRITE(pwm3_invert, 2, 0x5e, 0, 0x01, 4, bitmask),
 
-       PWRITEM(pwm1_enable, 0, PRI_LOW, VAA(0x5c, 0x5c, 0x62), VAA(0, 0, 0),
+       PWRITEM(pwm1_enable, 0, VAA(0x5c, 0x5c, 0x62), VAA(0, 0, 0),
                VAA(0x07, 0x01, 0x01), VAA(5, 3, 5), pwm_enable),
-       PWRITEM(pwm2_enable, 1, PRI_LOW, VAA(0x5d, 0x5d, 0x62), VAA(0, 0, 0),
+       PWRITEM(pwm2_enable, 1, VAA(0x5d, 0x5d, 0x62), VAA(0, 0, 0),
                VAA(0x07, 0x01, 0x01), VAA(5, 3, 6), pwm_enable),
-       PWRITEM(pwm3_enable, 2, PRI_LOW, VAA(0x5e, 0x5e, 0x62), VAA(0, 0, 0),
+       PWRITEM(pwm3_enable, 2, VAA(0x5e, 0x5e, 0x62), VAA(0, 0, 0),
                VAA(0x07, 0x01, 0x01), VAA(5, 3, 7), pwm_enable),
 
-       PWRITEM(pwm1_auto_channels, 0, PRI_LOW, VAA(0x5c, 0x5c), VAA(0, 0),
+       PWRITEM(pwm1_auto_channels, 0, VAA(0x5c, 0x5c), VAA(0, 0),
                VAA(0x07, 0x01), VAA(5, 3), pwm_ac),
-       PWRITEM(pwm2_auto_channels, 1, PRI_LOW, VAA(0x5d, 0x5d), VAA(0, 0),
+       PWRITEM(pwm2_auto_channels, 1, VAA(0x5d, 0x5d), VAA(0, 0),
                VAA(0x07, 0x01), VAA(5, 3), pwm_ac),
-       PWRITEM(pwm3_auto_channels, 2, PRI_LOW, VAA(0x5e, 0x5e), VAA(0, 0),
+       PWRITEM(pwm3_auto_channels, 2, VAA(0x5e, 0x5e), VAA(0, 0),
                VAA(0x07, 0x01), VAA(5, 3), pwm_ac),
 
-       PWRITE(pwm1_auto_point1_pwm, 0, PRI_LOW, 0x64, 0, 0, 0, u8),
-       PWRITE(pwm2_auto_point1_pwm, 1, PRI_LOW, 0x65, 0, 0, 0, u8),
-       PWRITE(pwm3_auto_point1_pwm, 2, PRI_LOW, 0x66, 0, 0, 0, u8),
-
-       PWRITE(pwm1_auto_point2_pwm, 0, PRI_LOW, 0x38, 0, 0, 0, u8),
-       PWRITE(pwm2_auto_point2_pwm, 1, PRI_LOW, 0x39, 0, 0, 0, u8),
-       PWRITE(pwm3_auto_point2_pwm, 2, PRI_LOW, 0x3a, 0, 0, 0, u8),
-
-       PWRITE(pwm1_freq, 0, PRI_LOW, 0x5f, 0, 0x0f, 0, pwm_freq),
-       PWRITE(pwm2_freq, 1, PRI_LOW, 0x60, 0, 0x0f, 0, pwm_freq),
-       PWRITE(pwm3_freq, 2, PRI_LOW, 0x61, 0, 0x0f, 0, pwm_freq),
-
-       PREAD(pwm1_auto_zone_assigned, 0, PRI_LOW, 0, 0, 0x03, 2, bitmask),
-       PREAD(pwm2_auto_zone_assigned, 1, PRI_LOW, 0, 0, 0x03, 4, bitmask),
-       PREAD(pwm3_auto_zone_assigned, 2, PRI_LOW, 0, 0, 0x03, 6, bitmask),
-
-       PWRITE(pwm1_auto_spinup_time, 0, PRI_LOW, 0x5c, 0, 0x07, 0, pwm_ast),
-       PWRITE(pwm2_auto_spinup_time, 1, PRI_LOW, 0x5d, 0, 0x07, 0, pwm_ast),
-       PWRITE(pwm3_auto_spinup_time, 2, PRI_LOW, 0x5e, 0, 0x07, 0, pwm_ast),
-
-       PWRITE(peci_enable, 0, PRI_LOW, 0x40, 0, 0x01, 4, bitmask),
-       PWRITE(peci_avg, 0, PRI_LOW, 0x36, 0, 0x07, 0, bitmask),
-       PWRITE(peci_domain, 0, PRI_LOW, 0x36, 0, 0x01, 3, bitmask),
-       PWRITE(peci_legacy, 0, PRI_LOW, 0x36, 0, 0x01, 4, bitmask),
-       PWRITE(peci_diode, 0, PRI_LOW, 0x0e, 0, 0x07, 4, bitmask),
-       PWRITE(peci_4domain, 0, PRI_LOW, 0x0e, 0, 0x01, 4, bitmask),
+       PWRITE(pwm1_auto_point1_pwm, 0, 0x64, 0, 0, 0, u8),
+       PWRITE(pwm2_auto_point1_pwm, 1, 0x65, 0, 0, 0, u8),
+       PWRITE(pwm3_auto_point1_pwm, 2, 0x66, 0, 0, 0, u8),
+
+       PWRITE(pwm1_auto_point2_pwm, 0, 0x38, 0, 0, 0, u8),
+       PWRITE(pwm2_auto_point2_pwm, 1, 0x39, 0, 0, 0, u8),
+       PWRITE(pwm3_auto_point2_pwm, 2, 0x3a, 0, 0, 0, u8),
+
+       PWRITE(pwm1_freq, 0, 0x5f, 0, 0x0f, 0, pwm_freq),
+       PWRITE(pwm2_freq, 1, 0x60, 0, 0x0f, 0, pwm_freq),
+       PWRITE(pwm3_freq, 2, 0x61, 0, 0x0f, 0, pwm_freq),
+
+       PREAD(pwm1_auto_zone_assigned, 0, 0, 0, 0x03, 2, bitmask),
+       PREAD(pwm2_auto_zone_assigned, 1, 0, 0, 0x03, 4, bitmask),
+       PREAD(pwm3_auto_zone_assigned, 2, 0, 0, 0x03, 6, bitmask),
+
+       PWRITE(pwm1_auto_spinup_time, 0, 0x5c, 0, 0x07, 0, pwm_ast),
+       PWRITE(pwm2_auto_spinup_time, 1, 0x5d, 0, 0x07, 0, pwm_ast),
+       PWRITE(pwm3_auto_spinup_time, 2, 0x5e, 0, 0x07, 0, pwm_ast),
+
+       PWRITE(peci_enable, 0, 0x40, 0, 0x01, 4, bitmask),
+       PWRITE(peci_avg, 0, 0x36, 0, 0x07, 0, bitmask),
+       PWRITE(peci_domain, 0, 0x36, 0, 0x01, 3, bitmask),
+       PWRITE(peci_legacy, 0, 0x36, 0, 0x01, 4, bitmask),
+       PWRITE(peci_diode, 0, 0x0e, 0, 0x07, 4, bitmask),
+       PWRITE(peci_4domain, 0, 0x0e, 0, 0x01, 4, bitmask),
+
+};
 
+/* List of registers to poll at high priority: */
+static const u8 asc7621_reglist_high[] = {
+    0x10, 0x25,                          /* temp1_input */
+    0x15, 0x26,                          /* temp2_input */
+    0x16, 0x27,                          /* temp3_input */
+    0x17, 0x33,                          /* temp4_input */
+    0x13, 0x20,                          /* in0_input */
+    0x18, 0x21,                          /* in1_input */
+    0x11, 0x22,                          /* in2_input */
+    0x12, 0x23,                          /* in3_input */
+    0x14, 0x24,                          /* in4_input */
+    0x28, 0x29,                          /* fan1_input */
+    0x2a, 0x2b,                          /* fan2_input */
+    0x2c, 0x2d,                          /* fan3_input */
+    0x2e, 0x2f,                          /* fan4_input */
+    0x30, 0x31, 0x32,                    /* pwm1-pwm3 */
+    0x41, 0x42, 0x43,                    /* alarms */
+    0xf6, 0xf7,                          /* temp5_input */
+    0xf8, 0xf9,                          /* temp6_input */
+    0xfa, 0xfb,                          /* temp7_input */
+    0xfc, 0xfd,                          /* temp8_input */
+};
+
+/* List of registers to poll at low priority: */
+static const u8 asc7621_reglist_low[] = {
+    0x02,                                /* temp1-temp4 source */
+    0x0e,                                /* temp5-temp8 enable, peci ctrl */
+    0x1c, 0x1d,                          /* remote1-remote2 offset */
+    0x34, 0x35,                          /* temp4 min/max */
+    0x36,                                /* peci */
+    0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d,  /* pwm control/smoothing */
+    0x40,                                /* peci_enable */
+    0x44, 0x45, 0x46, 0x47, 0x48, 0x49,  /* in0-in2 min/max */
+    0x4a, 0x4b, 0x4c, 0x4d,              /* in3-in4 min/max */
+    0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53,  /* temp1-temp3 min/max */
+    0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, /* fan1-fan4 min */
+    0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61,  /* pwm control */
+    0x62, 0x63, 0x64, 0x65, 0x66,        /* pwm control/smoothing */
+    0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c,  /* pwm control */
+    0x6d, 0x6e,                          /* temp1-temp4 hyst */
 };
 
 static struct asc7621_data *asc7621_update_device(struct device *dev)
@@ -1022,43 +1057,38 @@
        struct i2c_client *client = to_i2c_client(dev);
        struct asc7621_data *data = i2c_get_clientdata(client);
        int i;
+       u8 adr;
 
 /*
  * The asc7621 chips guarantee consistent reads of multi-byte values
  * regardless of the order of the reads.  No special logic is needed
  * so we can just read the registers in whatever  order they appear
- * in the asc7621_params array.
+ * in asc7621_reglist_high / asc7621_reglist_low.
  */
 
        mutex_lock(&data->update_lock);
 
        /* Read all the high priority registers */
 
-       if (!data->valid ||
-           time_after(jiffies, data->last_high_reading + INTERVAL_HIGH)) {
-
-               for (i = 0; i < ARRAY_SIZE(asc7621_register_priorities); i++) {
-                       if (asc7621_register_priorities[i] == PRI_HIGH) {
-                               data->reg[i] =
-                                   i2c_smbus_read_byte_data(client, i) & 0xff;
-                       }
-               }
-               data->last_high_reading = jiffies;
-       };                      /* last_reading */
-
-       /* Read all the low priority registers. */
+        if (!data->valid ||
+            time_after(jiffies, data->last_high_reading + INTERVAL_HIGH)) {
+                for (i = 0; i < ARRAY_SIZE(asc7621_reglist_high); i++) {
+                        adr = asc7621_reglist_high[i];
+                        data->reg[adr] =
+                            i2c_smbus_read_byte_data(client, adr) & 0xff;
+                }
+                data->last_high_reading = jiffies;
+        }
 
-       if (!data->valid ||
+        if (!data->valid ||
            time_after(jiffies, data->last_low_reading + INTERVAL_LOW)) {
-
-               for (i = 0; i < ARRAY_SIZE(asc7621_register_priorities); i++) {
-                       if (asc7621_register_priorities[i] == PRI_LOW) {
-                               data->reg[i] =
-                                   i2c_smbus_read_byte_data(client, i) & 0xff;
-                       }
-               }
-               data->last_low_reading = jiffies;
-       };                      /* last_reading */
+                for (i = 0; i < ARRAY_SIZE(asc7621_reglist_low); i++) {
+                       adr = asc7621_reglist_low[i];
+                       data->reg[adr] =
+                           i2c_smbus_read_byte_data(client, adr) & 0xff;
+                }
+                data->last_low_reading = jiffies;
+        };
 
        data->valid = 1;
 
@@ -1087,7 +1117,7 @@
 
 static void asc7621_init_client(struct i2c_client *client)
 {
-       int value, i, j;
+       int value;
 
        dev_dbg(&client->dev, "Initializing device\n");
 
@@ -1114,22 +1144,6 @@
        dev_dbg(&client->dev, "Setting READY to: 0x%02x\n", value);
        write_byte(0x40, value & 0xff);
 
-       dev_dbg(&client->dev, "Loading register arrays.\n");
-
-       /*
-        * Collect all the registers needed into a single array.
-        * This way, if a register isn't actually used for anything,
-        * we don't retrieve it.
-        */
-
-       for (i = 0; i < ARRAY_SIZE(asc7621_params); i++) {
-               for (j = 0; j < ARRAY_SIZE(asc7621_params[i].msb); j++)
-                       asc7621_register_priorities[asc7621_params[i].msb[j]] =
-                           asc7621_params[i].priority;
-               for (j = 0; j < ARRAY_SIZE(asc7621_params[i].lsb); j++)
-                       asc7621_register_priorities[asc7621_params[i].lsb[j]] =
-                           asc7621_params[i].priority;
-       }
 }
 
 static struct i2c_driver asc7621_driver;

[-- Attachment #4: Type: text/plain, Size: 153 bytes --]

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [lm-sensors] patch: asc7621 driver bug fixes
  2008-07-05 13:37 [lm-sensors] patch: asc7621 driver bug fixes Ken Milmore
@ 2008-07-06  6:15 ` Hans de Goede
  2008-07-06 16:49 ` Ken Milmore
                   ` (9 subsequent siblings)
  10 siblings, 0 replies; 12+ messages in thread
From: Hans de Goede @ 2008-07-06  6:15 UTC (permalink / raw)
  To: lm-sensors

Ken Milmore wrote:
> George,
> 
> Here are some suggested bug fixes for the asc7621 driver source which 
> you posted to lm_sensors on 29 May.
> (http://lists.lm-sensors.org/pipermail/lm-sensors/2008-May/023257.html)
> 

Thanks for doing this, I promised George to review this driver, but sofar 
haven't gotten around to doing this. Any chance you could do a complete review 
of it (or have you already done so, it sure looks that way :)

George, I assume you will post a new version of your patch soon with these 
fixes incorperated?

Regards,

Hans

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [lm-sensors] patch: asc7621 driver bug fixes
  2008-07-05 13:37 [lm-sensors] patch: asc7621 driver bug fixes Ken Milmore
  2008-07-06  6:15 ` Hans de Goede
@ 2008-07-06 16:49 ` Ken Milmore
  2008-07-06 18:03 ` George Joseph
                   ` (8 subsequent siblings)
  10 siblings, 0 replies; 12+ messages in thread
From: Ken Milmore @ 2008-07-06 16:49 UTC (permalink / raw)
  To: lm-sensors

[-- Attachment #1: Type: text/plain, Size: 3294 bytes --]

Hans / George,

Although things are shaping up nicely, I think there is still quite a 
bit left to do to completely review the driver.

The aSC7621 appears to have a lot of bells and whistles for closed loop
temperature control etc. and George has tried to support as much of this
feature set as possible.  Unfortunately that means there's quite a lot
to review - I haven't really looked at PWM control yet.

In any case, a few more things have come to light:

1.  In my last posting I wasn't completely sure if my "patch #2" was a
good idea or not.  Having looked at the Andigilog specs, I have become
convinced that it will be necessary after all.  The main problem is with 
the two-byte registers.  To get the read-locking to work, the MSB and 
LSB must be read together, one after the other (although it is 
unimportant which one is read first!).  So it is necessary to sequence 
the reads so that the two-byte registers are handled correctly.  My 
patch will correct this problem, although as I have said, I think that 
other, cleaner solutions may be possible.

2.  The voltage scaling used in the driver appears to be slightly off
from the nominal values given in the spec.  To get the scaling exactly
right, it is necessary to use a "muldiv" scheme which I've included in
the patch (see below).

3.  I fixed a problem in store_fan16() whereby it wasn't possible to set
the minimum fan RPM to zero.  The aSC7621 explicitly allows this, and of
course it is useful to prevent spurious alarms for fans which are
missing or stopped.

The attached patch rolls up all my changes so far.  It applies against
George's original patch of 29 May.  It includes all the bug fixes from
my last submission, the voltage scaling and fan fixes mentioned above
and some tidying up of the high and low priority register lists.


To sum up, by now I'm very happy with the following functions of the driver:

* Reading of voltages, fans and temperatures.

* Setting of alarm limits and display of alarms.

The following areas still need to be reviewed and perhaps improved:

* PWM control.  A few scary lookup tables in the code that I don't
understand yet!

* The miscellaneous configuration knobs (Input selection, PECI config 
etc).  I haven't tested these.

* I think there is space for improvement in how the low-priority
register list is handled.  A full minute between updates is probably too
long.

I hope this is of some use.  I will try to look at reviewing the 
remaining parts of the driver as time allows.

George, I'd appreciate your feedback on my patches so far; I realise you 
might want to approach some of these changes differently to the way I've 
done them.

Best wishes,

Ken.


Hans de Goede wrote:
> Ken Milmore wrote:
>> George,
>>
>> Here are some suggested bug fixes for the asc7621 driver source which 
>> you posted to lm_sensors on 29 May.
>> (http://lists.lm-sensors.org/pipermail/lm-sensors/2008-May/023257.html)
>>
> 
> Thanks for doing this, I promised George to review this driver, but 
> sofar haven't gotten around to doing this. Any chance you could do a 
> complete review of it (or have you already done so, it sure looks that 
> way :)
> 
> George, I assume you will post a new version of your patch soon with 
> these fixes incorperated?
> 
> Regards,
> 
> Hans
> 



[-- Attachment #2: asc7621.diff --]
[-- Type: text/plain, Size: 30979 bytes --]

--- asc7621.c.orig	2008-07-06 13:19:00.000000000 +0100
+++ asc7621.c	2008-07-06 17:29:09.000000000 +0100
@@ -121,7 +121,6 @@
 */
 struct asc7621_param {
        struct sensor_device_attribute sda;
-       u8 priority;
        u8 msb[3];
        u8 lsb[3];
        u8 mask[3];
@@ -130,12 +129,6 @@
        char *label;
 };
 
-/*
- * This is the map that ultimately indicates whether we'll be
- * retrieving a register value or not, and at what frequency.
- */
-static u8 asc7621_register_priorities[255];
-
 static struct asc7621_data *asc7621_update_device(struct device *dev);
 
 #define read_byte(reg) (i2c_smbus_read_byte_data(client, reg) & 0xff)
@@ -244,10 +237,15 @@
 {
        SETUP_STORE_data_param(dev, attr);
 
-       u16 reqval = simple_strtoul(buf, NULL, 10);
+       u32 reqval = simple_strtoul(buf, NULL, 10);
 
-       reqval =
-           (SENSORS_LIMIT((reqval) <= 0 ? 0 : 5400000 / (reqval), 0, 65534));
+       /* If a minimum RPM of zero is requested, then we set the register to
+          0xffff. This value allows the fan to be stopped completely without
+          generating an alarm. If any value other than 0 is requested, then
+          we limit the calculated tacho period to 0xfffe, which equates to a
+          minimum rotation speed of about 82 RPM. */
+       reqval = (reqval == 0 ? 0xffff :
+                      SENSORS_LIMIT(5400000 / reqval, 0, 0xfffe));
 
        mutex_lock(&data->update_lock);
        data->reg[param->msb[0]] = (reqval >> 8) & 0xff;
@@ -259,38 +257,42 @@
        return count;
 }
 
-/* Voltages are scaled in the device so that the nominal voltage
- * is 3/4ths of the 0-255 range (I.E 192).
- * If all voltages are 'normal' then all voltage registers will
- * read 0xC0.  This doesn't help us if we don't have a point of refernce.
- * The data sheet however provides us with the full scale value for each
- * which is stored in in_scaling.  The sda->index parameter value provides
- * the index into in_scaling.
+/* Voltages are scaled in the device so that the nominal voltage is
+ * 3/4ths of the 0-255 range (I.E 192 in the MSB).
+ * If all voltages are 'normal' then all voltage registers will read
+ * 0xC0 in the MSB.
  *
- * NOTE: The chip expects the first 2 inputs be 2.5 and 2.25 volts
- * respectively. That doesn't mean that's what the motherboard provides. :)
+ * The tables below scale the voltages readings so that the 3/4 scale
+ * reading maps exactly to the Nominal Voltage for each sensor given in
+ * the datasheet, as follows:
+ *
+ * in0 (2.5V)  : 192 * 625 / 48 =  2500
+ * in1 (2.25V) : 192 * 375 / 32 =  2250
+ * in2 (3.3V)  : 192 * 275 / 16 =  3300
+ * in4 (5V)    : 192 * 625 / 24 =  5000
+ * in5 (12V)   : 192 * 125 /  2 = 12000
+ *
+ * To scale the 10-bit registers, we multiply the divisors by 4.
+ * When writing the limit registers, we interchange multipliers and divisors.
  */
 
-static int asc7621_in_scaling[] = {
-       3320, 3000, 4380, 6640, 16000
-};
+static s32 asc7621_in_scaling_mul[] = { 625, 375, 275, 625, 125 };
+static s32 asc7621_in_scaling_div[] = {  48,  32,  16,  24,   2 };
 
 static ssize_t show_in10(struct device *dev, struct device_attribute *attr,
                         char *buf)
 {
-       SETUP_SHOW_data_param(dev, attr);
+       u8 nr;
+       s32 regval;
 
-       u8 nr = sda->index;
-       u16 regval = (data->reg[param->msb[0]] * asc7621_in_scaling[nr]) / 256;
+       SETUP_SHOW_data_param(dev, attr);
 
-       /* The LSB value is a 2-bit scaling of the MSB's LSbit value.
-        * I.E.  If the maximim voltage for this input is 6640 millivolts then
-        * a MSB register value of 0 = 0mv and 255 = 6640mv.
-        * A 1 step change therefore represents 25.9mv (6640 / 256).
-        * The extra 2-bits therefore represent increments of 6.48mv.
-        */
-       regval += ((asc7621_in_scaling[nr] / 256) / 4) *
-           (data->reg[param->lsb[0]] >> 6);
+       nr = sda->index;
+       /* The LSB value is a 2-bit scaling of the MSB's LSbit value. */
+       regval = (data->reg[param->msb[0]] << 2) +
+                (data->reg[param->lsb[0]] >> 6);
+       regval *= asc7621_in_scaling_mul[nr];
+       regval /= (asc7621_in_scaling_div[nr] * 4);
 
        return sprintf(buf, "%u\n", regval);
 }
@@ -299,24 +301,32 @@
 static ssize_t show_in8(struct device *dev, struct device_attribute *attr,
                        char *buf)
 {
+       u8 nr;
+       unsigned int regval;
+
        SETUP_SHOW_data_param(dev, attr);
 
-       u8 nr = sda->index;
+       nr = sda->index;
+       regval = data->reg[param->msb[0]];
+       regval *= asc7621_in_scaling_mul[nr];
+       regval /= asc7621_in_scaling_div[nr];
 
-       return sprintf(buf, "%u\n",
-                      ((data->reg[param->msb[0]] *
-                        asc7621_in_scaling[nr]) / 256));
+       return sprintf(buf, "%u\n", regval);
 }
 
 static ssize_t store_in8(struct device *dev, struct device_attribute *attr,
                         const char *buf, size_t count)
 {
+       u8 nr;
+       s32 reqval;
+
        SETUP_STORE_data_param(dev, attr);
 
-       u8 nr = sda->index;
-       u8 reqval = simple_strtoul(buf, NULL, 10);
-       reqval =
-           SENSORS_LIMIT(((reqval * 256) / asc7621_in_scaling[nr]), 0, 255);
+       nr = sda->index;
+       reqval = simple_strtoul(buf, NULL, 10);
+       reqval *= asc7621_in_scaling_div[nr];
+       reqval /= asc7621_in_scaling_mul[nr];
+       reqval = SENSORS_LIMIT(reqval, 0, 255);
 
        mutex_lock(&data->update_lock);
        data->reg[param->msb[0]] = reqval;
@@ -385,12 +395,14 @@
                            struct device_attribute *attr, const char *buf,
                            size_t count)
 {
+       s32 reqval;
+       s32 i, f;
+       s8 temp;
+
        SETUP_STORE_data_param(dev, attr);
 
-       s32 reqval = simple_strtol(buf, NULL, 10);
+       reqval = simple_strtol(buf, NULL, 10);
        reqval = SENSORS_LIMIT(reqval, -32000, 31750);
-       s32 i, f;
-       s8 temp;
        i = reqval / 1000;
        f = reqval - (i * 1000);
        temp = i << 2;
@@ -781,38 +793,38 @@
  */
 #define VAA(args...) {args}
 
-#define PREAD(name, n, pri, rm, rl, m, s, r) \
+#define PREAD(name, n, rm, rl, m, s, r) \
        {.sda = SENSOR_ATTR(name, S_IRUGO, show_##r, NULL, n), \
-         .priority = pri,.msb[0] = rm, .lsb[0] = rl, .mask[0] = m, \
+         .msb[0] = rm, .lsb[0] = rl, .mask[0] = m, \
          .shift[0] = s,}
 
-#define PWRITE(name, n, pri, rm, rl, m, s, r) \
+#define PWRITE(name, n, rm, rl, m, s, r) \
        {.sda = SENSOR_ATTR(name, S_IRUGO | S_IWUSR, show_##r, store_##r, n), \
-         .priority = pri,.msb[0] = rm, .lsb[0] = rl, .mask[0] = m, \
+         .msb[0] = rm, .lsb[0] = rl, .mask[0] = m, \
          .shift[0] = s,}
 
 /*
  * PWRITEM assumes that the initializers for the .msb, .lsb, .mask and .shift
  * were created using the VAA macro.
  */
-#define PWRITEM(name, n, pri, rm, rl, m, s, r) \
+#define PWRITEM(name, n, rm, rl, m, s, r) \
        {.sda = SENSOR_ATTR(name, S_IRUGO | S_IWUSR, show_##r, store_##r, n), \
-         .priority = pri,.msb = rm, .lsb = rl, .mask = m, .shift = s,}
+         .msb = rm, .lsb = rl, .mask = m, .shift = s,}
 
 #define PCONST(name, n, v) \
        {.sda = SENSOR_ATTR(name, S_IRUGO, show_const, NULL, n), \
-         .priority = PRI_LOW, .value = v,}
+         .value = v,}
 
 #define PLABEL(name, n, v) \
        {.sda = SENSOR_ATTR(name, S_IRUGO, show_label, NULL, n), \
-         .priority = PRI_LOW, .label = v,}
+         .label = v,}
 
 static struct asc7621_param asc7621_params[] = {
-       PREAD(in0_input, 0, PRI_HIGH, 0x20, 0x13, 0, 0, in10),
-       PREAD(in1_input, 1, PRI_HIGH, 0x21, 0x18, 0, 0, in10),
-       PREAD(in2_input, 2, PRI_HIGH, 0x22, 0x11, 0, 0, in10),
-       PREAD(in3_input, 3, PRI_HIGH, 0x23, 0x12, 0, 0, in10),
-       PREAD(in4_input, 4, PRI_HIGH, 0x24, 0x14, 0, 0, in10),
+       PREAD(in0_input, 0, 0x20, 0x13, 0, 0, in10),
+       PREAD(in1_input, 1, 0x21, 0x18, 0, 0, in10),
+       PREAD(in2_input, 2, 0x22, 0x11, 0, 0, in10),
+       PREAD(in3_input, 3, 0x23, 0x12, 0, 0, in10),
+       PREAD(in4_input, 4, 0x24, 0x14, 0, 0, in10),
 
        PLABEL(in0_label, 0, "in0"),
        PLABEL(in1_label, 1, "in1"),
@@ -820,52 +832,52 @@
        PLABEL(in3_label, 3, "in3"),
        PLABEL(in4_label, 4, "in4"),
 
-       PWRITE(in0_min, 0, PRI_LOW, 0x44, 0, 0, 0, in8),
-       PWRITE(in1_min, 1, PRI_LOW, 0x46, 0, 0, 0, in8),
-       PWRITE(in2_min, 2, PRI_LOW, 0x48, 0, 0, 0, in8),
-       PWRITE(in3_min, 3, PRI_LOW, 0x4a, 0, 0, 0, in8),
-       PWRITE(in4_min, 4, PRI_LOW, 0x4c, 0, 0, 0, in8),
-
-       PWRITE(in0_max, 0, PRI_LOW, 0x45, 0, 0, 0, in8),
-       PWRITE(in1_max, 1, PRI_LOW, 0x47, 0, 0, 0, in8),
-       PWRITE(in2_max, 2, PRI_LOW, 0x49, 0, 0, 0, in8),
-       PWRITE(in3_max, 3, PRI_LOW, 0x4b, 0, 0, 0, in8),
-       PWRITE(in4_max, 4, PRI_LOW, 0x4d, 0, 0, 0, in8),
-
-       PREAD(in0_alarm, 0, PRI_LOW, 0x41, 0, 0x01, 0, bitmask),
-       PREAD(in1_alarm, 1, PRI_LOW, 0x41, 0, 0x01, 1, bitmask),
-       PREAD(in2_alarm, 2, PRI_LOW, 0x41, 0, 0x01, 2, bitmask),
-       PREAD(in3_alarm, 3, PRI_LOW, 0x41, 0, 0x01, 3, bitmask),
-       PREAD(in4_alarm, 4, PRI_LOW, 0x42, 0, 0x01, 0, bitmask),
-
-       PREAD(fan1_input, 0, PRI_HIGH, 0x29, 0x28, 0, 0, fan16),
-       PREAD(fan2_input, 1, PRI_HIGH, 0x2b, 0x2a, 0, 0, fan16),
-       PREAD(fan3_input, 2, PRI_HIGH, 0x2d, 0x2c, 0, 0, fan16),
-       PREAD(fan4_input, 3, PRI_HIGH, 0x2f, 0x2e, 0, 0, fan16),
+       PWRITE(in0_min, 0, 0x44, 0, 0, 0, in8),
+       PWRITE(in1_min, 1, 0x46, 0, 0, 0, in8),
+       PWRITE(in2_min, 2, 0x48, 0, 0, 0, in8),
+       PWRITE(in3_min, 3, 0x4a, 0, 0, 0, in8),
+       PWRITE(in4_min, 4, 0x4c, 0, 0, 0, in8),
+
+       PWRITE(in0_max, 0, 0x45, 0, 0, 0, in8),
+       PWRITE(in1_max, 1, 0x47, 0, 0, 0, in8),
+       PWRITE(in2_max, 2, 0x49, 0, 0, 0, in8),
+       PWRITE(in3_max, 3, 0x4b, 0, 0, 0, in8),
+       PWRITE(in4_max, 4, 0x4d, 0, 0, 0, in8),
+
+       PREAD(in0_alarm, 0, 0x41, 0, 0x01, 0, bitmask),
+       PREAD(in1_alarm, 1, 0x41, 0, 0x01, 1, bitmask),
+       PREAD(in2_alarm, 2, 0x41, 0, 0x01, 2, bitmask),
+       PREAD(in3_alarm, 3, 0x41, 0, 0x01, 3, bitmask),
+       PREAD(in4_alarm, 4, 0x42, 0, 0x01, 0, bitmask),
+
+       PREAD(fan1_input, 0, 0x29, 0x28, 0, 0, fan16),
+       PREAD(fan2_input, 1, 0x2b, 0x2a, 0, 0, fan16),
+       PREAD(fan3_input, 2, 0x2d, 0x2c, 0, 0, fan16),
+       PREAD(fan4_input, 3, 0x2f, 0x2e, 0, 0, fan16),
 
        PLABEL(fan1_label, 0, "fan1"),
        PLABEL(fan2_label, 1, "fan2"),
        PLABEL(fan3_label, 2, "fan3"),
        PLABEL(fan4_label, 3, "fan4"),
 
-       PWRITE(fan1_min, 0, PRI_LOW, 0x55, 0x54, 0, 0, fan16),
-       PWRITE(fan2_min, 1, PRI_LOW, 0x57, 0x56, 0, 0, fan16),
-       PWRITE(fan3_min, 2, PRI_LOW, 0x59, 0x58, 0, 0, fan16),
-       PWRITE(fan4_min, 3, PRI_LOW, 0x5b, 0x5a, 0, 0, fan16),
-
-       PREAD(fan1_alarm, 0, PRI_LOW, 0x42, 0, 0x01, 0, bitmask),
-       PREAD(fan2_alarm, 1, PRI_LOW, 0x42, 0, 0x01, 1, bitmask),
-       PREAD(fan3_alarm, 2, PRI_LOW, 0x42, 0, 0x01, 2, bitmask),
-       PREAD(fan4_alarm, 3, PRI_LOW, 0x42, 0, 0x01, 3, bitmask),
-
-       PREAD(temp1_input, 0, PRI_HIGH, 0x25, 0x10, 0, 0, temp10),
-       PREAD(temp2_input, 1, PRI_HIGH, 0x26, 0x15, 0, 0, temp10),
-       PREAD(temp3_input, 2, PRI_HIGH, 0x27, 0x16, 0, 0, temp10),
-       PREAD(temp4_input, 3, PRI_HIGH, 0x33, 0x17, 0, 0, temp10),
-       PREAD(temp5_input, 4, PRI_HIGH, 0xf7, 0xf6, 0, 0, temp10),
-       PREAD(temp6_input, 5, PRI_HIGH, 0xf9, 0xf8, 0, 0, temp10),
-       PREAD(temp7_input, 6, PRI_HIGH, 0xfb, 0xfa, 0, 0, temp10),
-       PREAD(temp8_input, 7, PRI_HIGH, 0xfd, 0xfc, 0, 0, temp10),
+       PWRITE(fan1_min, 0, 0x55, 0x54, 0, 0, fan16),
+       PWRITE(fan2_min, 1, 0x57, 0x56, 0, 0, fan16),
+       PWRITE(fan3_min, 2, 0x59, 0x58, 0, 0, fan16),
+       PWRITE(fan4_min, 3, 0x5b, 0x5a, 0, 0, fan16),
+
+       PREAD(fan1_alarm, 0, 0x42, 0, 0x01, 2, bitmask),
+       PREAD(fan2_alarm, 1, 0x42, 0, 0x01, 3, bitmask),
+       PREAD(fan3_alarm, 2, 0x42, 0, 0x01, 4, bitmask),
+       PREAD(fan4_alarm, 3, 0x42, 0, 0x01, 5, bitmask),
+
+       PREAD(temp1_input, 0, 0x25, 0x10, 0, 0, temp10),
+       PREAD(temp2_input, 1, 0x26, 0x15, 0, 0, temp10),
+       PREAD(temp3_input, 2, 0x27, 0x16, 0, 0, temp10),
+       PREAD(temp4_input, 3, 0x33, 0x17, 0, 0, temp10),
+       PREAD(temp5_input, 4, 0xf7, 0xf6, 0, 0, temp10),
+       PREAD(temp6_input, 5, 0xf9, 0xf8, 0, 0, temp10),
+       PREAD(temp7_input, 6, 0xfb, 0xfa, 0, 0, temp10),
+       PREAD(temp8_input, 7, 0xfd, 0xfc, 0, 0, temp10),
 
        PCONST(temp1_type, 0, 1),
        PCONST(temp2_type, 1, 3),
@@ -885,134 +897,192 @@
        PLABEL(temp7_label, 6, "peci 3"),
        PLABEL(temp8_label, 7, "peci 4"),
 
-       PWRITE(temp1_min, 0, PRI_LOW, 0x4e, 0, 0, 0, temp8),
-       PWRITE(temp2_min, 1, PRI_LOW, 0x50, 0, 0, 0, temp8),
-       PWRITE(temp3_min, 2, PRI_LOW, 0x52, 0, 0, 0, temp8),
-       PWRITE(temp4_min, 3, PRI_LOW, 0x34, 0, 0, 0, temp8),
-
-       PWRITE(temp1_max, 0, PRI_LOW, 0x4f, 0, 0, 0, temp8),
-       PWRITE(temp2_max, 1, PRI_LOW, 0x51, 0, 0, 0, temp8),
-       PWRITE(temp3_max, 2, PRI_LOW, 0x53, 0, 0, 0, temp8),
-       PWRITE(temp4_max, 3, PRI_LOW, 0x35, 0, 0, 0, temp8),
-
-       PREAD(temp1_alarm, 0, PRI_LOW, 0x41, 0, 0x01, 4, bitmask),
-       PREAD(temp2_alarm, 1, PRI_LOW, 0x41, 0, 0x01, 5, bitmask),
-       PREAD(temp3_alarm, 2, PRI_LOW, 0x41, 0, 0x01, 6, bitmask),
-       PREAD(temp4_alarm, 3, PRI_LOW, 0x43, 0, 0x01, 0, bitmask),
-
-       PWRITE(temp1_source, 0, PRI_LOW, 0x02, 0, 0x07, 4, bitmask),
-       PWRITE(temp2_source, 1, PRI_LOW, 0x02, 0, 0x07, 0, bitmask),
-       PWRITE(temp3_source, 2, PRI_LOW, 0x03, 0, 0x07, 4, bitmask),
-       PWRITE(temp4_source, 3, PRI_LOW, 0x03, 0, 0x07, 0, bitmask),
+       PWRITE(temp1_min, 0, 0x4e, 0, 0, 0, temp8),
+       PWRITE(temp2_min, 1, 0x50, 0, 0, 0, temp8),
+       PWRITE(temp3_min, 2, 0x52, 0, 0, 0, temp8),
+       PWRITE(temp4_min, 3, 0x34, 0, 0, 0, temp8),
+
+       PWRITE(temp1_max, 0, 0x4f, 0, 0, 0, temp8),
+       PWRITE(temp2_max, 1, 0x51, 0, 0, 0, temp8),
+       PWRITE(temp3_max, 2, 0x53, 0, 0, 0, temp8),
+       PWRITE(temp4_max, 3, 0x35, 0, 0, 0, temp8),
+
+       PREAD(temp1_alarm, 0, 0x41, 0, 0x01, 4, bitmask),
+       PREAD(temp2_alarm, 1, 0x41, 0, 0x01, 5, bitmask),
+       PREAD(temp3_alarm, 2, 0x41, 0, 0x01, 6, bitmask),
+       PREAD(temp4_alarm, 3, 0x43, 0, 0x01, 0, bitmask),
+
+       PWRITE(temp1_source, 0, 0x02, 0, 0x07, 4, bitmask),
+       PWRITE(temp2_source, 1, 0x02, 0, 0x07, 0, bitmask),
+       PWRITE(temp3_source, 2, 0x03, 0, 0x07, 4, bitmask),
+       PWRITE(temp4_source, 3, 0x03, 0, 0x07, 0, bitmask),
        PCONST(temp5_source, 4, 4),
        PCONST(temp6_source, 5, 5),
        PCONST(temp7_source, 6, 6),
        PCONST(temp8_source, 7, 7),
 
-       PWRITE(temp1_smoothing_enable, 0, PRI_LOW, 0x62, 0, 0x01, 3, bitmask),
-       PWRITE(temp2_smoothing_enable, 1, PRI_LOW, 0x63, 0, 0x01, 7, bitmask),
-       PWRITE(temp3_smoothing_enable, 2, PRI_LOW, 0x64, 0, 0x01, 3, bitmask),
-       PWRITE(temp4_smoothing_enable, 3, PRI_LOW, 0x3c, 0, 0x01, 3, bitmask),
-
-       PWRITE(temp1_smoothing_time, 0, PRI_LOW, 0x62, 0, 0x07, 0, temp_st),
-       PWRITE(temp2_smoothing_time, 1, PRI_LOW, 0x63, 0, 0x07, 4, temp_st),
-       PWRITE(temp3_smoothing_time, 2, PRI_LOW, 0x63, 0, 0x07, 0, temp_st),
-       PWRITE(temp4_smoothing_time, 3, PRI_LOW, 0x3c, 0, 0x07, 0, temp_st),
+       PWRITE(temp1_smoothing_enable, 0, 0x62, 0, 0x01, 3, bitmask),
+       PWRITE(temp2_smoothing_enable, 1, 0x63, 0, 0x01, 7, bitmask),
+       PWRITE(temp3_smoothing_enable, 2, 0x63, 0, 0x01, 3, bitmask),
+       PWRITE(temp4_smoothing_enable, 3, 0x3c, 0, 0x01, 3, bitmask),
+
+       PWRITE(temp1_smoothing_time, 0, 0x62, 0, 0x07, 0, temp_st),
+       PWRITE(temp2_smoothing_time, 1, 0x63, 0, 0x07, 4, temp_st),
+       PWRITE(temp3_smoothing_time, 2, 0x63, 0, 0x07, 0, temp_st),
+       PWRITE(temp4_smoothing_time, 3, 0x3c, 0, 0x07, 0, temp_st),
 
-       PWRITE(temp1_auto_point1_temp_hyst, 0, PRI_LOW, 0x6d, 0, 0x0f, 4,
+       PWRITE(temp1_auto_point1_temp_hyst, 0, 0x6d, 0, 0x0f, 4,
               bitmask),
-       PWRITE(temp2_auto_point1_temp_hyst, 1, PRI_LOW, 0x6d, 0, 0x0f, 0,
+       PWRITE(temp2_auto_point1_temp_hyst, 1, 0x6d, 0, 0x0f, 0,
               bitmask),
-       PWRITE(temp3_auto_point1_temp_hyst, 2, PRI_LOW, 0x6e, 0, 0x0f, 4,
+       PWRITE(temp3_auto_point1_temp_hyst, 2, 0x6e, 0, 0x0f, 4,
               bitmask),
-       PWRITE(temp4_auto_point1_temp_hyst, 3, PRI_LOW, 0x6e, 0, 0x0f, 0,
+       PWRITE(temp4_auto_point1_temp_hyst, 3, 0x6e, 0, 0x0f, 0,
               bitmask),
 
-       PREAD(temp1_auto_point2_temp_hyst, 0, PRI_LOW, 0x6d, 0, 0x0f, 4,
+       PREAD(temp1_auto_point2_temp_hyst, 0, 0x6d, 0, 0x0f, 4,
              bitmask),
-       PREAD(temp2_auto_point2_temp_hyst, 1, PRI_LOW, 0x6d, 0, 0x0f, 0,
+       PREAD(temp2_auto_point2_temp_hyst, 1, 0x6d, 0, 0x0f, 0,
              bitmask),
-       PREAD(temp3_auto_point2_temp_hyst, 2, PRI_LOW, 0x6e, 0, 0x0f, 4,
+       PREAD(temp3_auto_point2_temp_hyst, 2, 0x6e, 0, 0x0f, 4,
              bitmask),
-       PREAD(temp4_auto_point2_temp_hyst, 3, PRI_LOW, 0x6e, 0, 0x0f, 0,
+       PREAD(temp4_auto_point2_temp_hyst, 3, 0x6e, 0, 0x0f, 0,
              bitmask),
 
-       PWRITE(temp1_auto_point1_temp, 0, PRI_LOW, 0x67, 0, 0, 0, temp8),
-       PWRITE(temp2_auto_point1_temp, 1, PRI_LOW, 0x68, 0, 0, 0, temp8),
-       PWRITE(temp3_auto_point1_temp, 2, PRI_LOW, 0x69, 0, 0, 0, temp8),
-       PWRITE(temp4_auto_point1_temp, 3, PRI_LOW, 0x3b, 0, 0, 0, temp8),
+       PWRITE(temp1_auto_point1_temp, 0, 0x67, 0, 0, 0, temp8),
+       PWRITE(temp2_auto_point1_temp, 1, 0x68, 0, 0, 0, temp8),
+       PWRITE(temp3_auto_point1_temp, 2, 0x69, 0, 0, 0, temp8),
+       PWRITE(temp4_auto_point1_temp, 3, 0x3b, 0, 0, 0, temp8),
 
-       PWRITEM(temp1_auto_point2_temp, 0, PRI_LOW, VAA(0x5f, 0x67), VAA(0),
+       PWRITEM(temp1_auto_point2_temp, 0, VAA(0x5f, 0x67), VAA(0),
                VAA(0x0f), VAA(4), ap2_temp),
-       PWRITEM(temp2_auto_point2_temp, 1, PRI_LOW, VAA(0x60, 0x68), VAA(0),
+       PWRITEM(temp2_auto_point2_temp, 1, VAA(0x60, 0x68), VAA(0),
                VAA(0x0f), VAA(4), ap2_temp),
-       PWRITEM(temp3_auto_point2_temp, 2, PRI_LOW, VAA(0x61, 0x69), VAA(0),
+       PWRITEM(temp3_auto_point2_temp, 2, VAA(0x61, 0x69), VAA(0),
                VAA(0x0f), VAA(4), ap2_temp),
-       PWRITEM(temp4_auto_point2_temp, 3, PRI_LOW, VAA(0x3c, 0x3b), VAA(0),
+       PWRITEM(temp4_auto_point2_temp, 3, VAA(0x3c, 0x3b), VAA(0),
                VAA(0x0f), VAA(4), ap2_temp),
 
-       PWRITE(temp1_crit, 0, PRI_LOW, 0x6a, 0, 0, 0, temp8),
-       PWRITE(temp2_crit, 1, PRI_LOW, 0x6b, 0, 0, 0, temp8),
-       PWRITE(temp3_crit, 2, PRI_LOW, 0x6c, 0, 0, 0, temp8),
-       PWRITE(temp4_crit, 3, PRI_LOW, 0x3d, 0, 0, 0, temp8),
-
-       PWRITE(temp5_enable, 4, PRI_LOW, 0x0e, 0, 0x01, 0, bitmask),
-       PWRITE(temp6_enable, 5, PRI_LOW, 0x0e, 0, 0x01, 1, bitmask),
-       PWRITE(temp7_enable, 6, PRI_LOW, 0x0e, 0, 0x01, 2, bitmask),
-       PWRITE(temp8_enable, 7, PRI_LOW, 0x0e, 0, 0x01, 3, bitmask),
-
-       PWRITE(remote1_offset, 0, PRI_LOW, 0x1c, 0, 0, 0, temp62),
-       PWRITE(remote2_offset, 1, PRI_LOW, 0x1d, 0, 0, 0, temp62),
-
-       PWRITE(pwm1, 0, PRI_HIGH, 0x30, 0, 0, 0, u8),
-       PWRITE(pwm2, 1, PRI_HIGH, 0x31, 0, 0, 0, u8),
-       PWRITE(pwm3, 2, PRI_HIGH, 0x32, 0, 0, 0, u8),
-
-       PWRITE(pwm1_invert, 0, PRI_LOW, 0x5c, 0, 0x01, 4, bitmask),
-       PWRITE(pwm2_invert, 1, PRI_LOW, 0x5d, 0, 0x01, 4, bitmask),
-       PWRITE(pwm3_invert, 2, PRI_LOW, 0x5e, 0, 0x01, 4, bitmask),
+       PWRITE(temp1_crit, 0, 0x6a, 0, 0, 0, temp8),
+       PWRITE(temp2_crit, 1, 0x6b, 0, 0, 0, temp8),
+       PWRITE(temp3_crit, 2, 0x6c, 0, 0, 0, temp8),
+       PWRITE(temp4_crit, 3, 0x3d, 0, 0, 0, temp8),
+
+       PWRITE(temp5_enable, 4, 0x0e, 0, 0x01, 0, bitmask),
+       PWRITE(temp6_enable, 5, 0x0e, 0, 0x01, 1, bitmask),
+       PWRITE(temp7_enable, 6, 0x0e, 0, 0x01, 2, bitmask),
+       PWRITE(temp8_enable, 7, 0x0e, 0, 0x01, 3, bitmask),
+
+       PWRITE(remote1_offset, 0, 0x1c, 0, 0, 0, temp62),
+       PWRITE(remote2_offset, 1, 0x1d, 0, 0, 0, temp62),
+
+       PWRITE(pwm1, 0, 0x30, 0, 0, 0, u8),
+       PWRITE(pwm2, 1, 0x31, 0, 0, 0, u8),
+       PWRITE(pwm3, 2, 0x32, 0, 0, 0, u8),
+
+       PWRITE(pwm1_invert, 0, 0x5c, 0, 0x01, 4, bitmask),
+       PWRITE(pwm2_invert, 1, 0x5d, 0, 0x01, 4, bitmask),
+       PWRITE(pwm3_invert, 2, 0x5e, 0, 0x01, 4, bitmask),
 
-       PWRITEM(pwm1_enable, 0, PRI_LOW, VAA(0x5c, 0x5c, 0x62), VAA(0, 0, 0),
+       PWRITEM(pwm1_enable, 0, VAA(0x5c, 0x5c, 0x62), VAA(0, 0, 0),
                VAA(0x07, 0x01, 0x01), VAA(5, 3, 5), pwm_enable),
-       PWRITEM(pwm2_enable, 1, PRI_LOW, VAA(0x5d, 0x5d, 0x62), VAA(0, 0, 0),
+       PWRITEM(pwm2_enable, 1, VAA(0x5d, 0x5d, 0x62), VAA(0, 0, 0),
                VAA(0x07, 0x01, 0x01), VAA(5, 3, 6), pwm_enable),
-       PWRITEM(pwm3_enable, 2, PRI_LOW, VAA(0x5e, 0x5e, 0x62), VAA(0, 0, 0),
+       PWRITEM(pwm3_enable, 2, VAA(0x5e, 0x5e, 0x62), VAA(0, 0, 0),
                VAA(0x07, 0x01, 0x01), VAA(5, 3, 7), pwm_enable),
 
-       PWRITEM(pwm1_auto_channels, 0, PRI_LOW, VAA(0x5c, 0x5c), VAA(0, 0),
+       PWRITEM(pwm1_auto_channels, 0, VAA(0x5c, 0x5c), VAA(0, 0),
                VAA(0x07, 0x01), VAA(5, 3), pwm_ac),
-       PWRITEM(pwm2_auto_channels, 1, PRI_LOW, VAA(0x5d, 0x5d), VAA(0, 0),
+       PWRITEM(pwm2_auto_channels, 1, VAA(0x5d, 0x5d), VAA(0, 0),
                VAA(0x07, 0x01), VAA(5, 3), pwm_ac),
-       PWRITEM(pwm3_auto_channels, 2, PRI_LOW, VAA(0x5e, 0x5e), VAA(0, 0),
+       PWRITEM(pwm3_auto_channels, 2, VAA(0x5e, 0x5e), VAA(0, 0),
                VAA(0x07, 0x01), VAA(5, 3), pwm_ac),
 
-       PWRITE(pwm1_auto_point1_pwm, 0, PRI_LOW, 0x64, 0, 0, 0, u8),
-       PWRITE(pwm2_auto_point1_pwm, 1, PRI_LOW, 0x65, 0, 0, 0, u8),
-       PWRITE(pwm3_auto_point1_pwm, 2, PRI_LOW, 0x66, 0, 0, 0, u8),
-
-       PWRITE(pwm1_auto_point2_pwm, 0, PRI_LOW, 0x38, 0, 0, 0, u8),
-       PWRITE(pwm2_auto_point2_pwm, 1, PRI_LOW, 0x39, 0, 0, 0, u8),
-       PWRITE(pwm3_auto_point2_pwm, 2, PRI_LOW, 0x3a, 0, 0, 0, u8),
-
-       PWRITE(pwm1_freq, 0, PRI_LOW, 0x5f, 0, 0x0f, 0, pwm_freq),
-       PWRITE(pwm2_freq, 1, PRI_LOW, 0x60, 0, 0x0f, 0, pwm_freq),
-       PWRITE(pwm3_freq, 2, PRI_LOW, 0x61, 0, 0x0f, 0, pwm_freq),
-
-       PREAD(pwm1_auto_zone_assigned, 0, PRI_LOW, 0, 0, 0x03, 2, bitmask),
-       PREAD(pwm2_auto_zone_assigned, 1, PRI_LOW, 0, 0, 0x03, 4, bitmask),
-       PREAD(pwm3_auto_zone_assigned, 2, PRI_LOW, 0, 0, 0x03, 6, bitmask),
-
-       PWRITE(pwm1_auto_spinup_time, 0, PRI_LOW, 0x5c, 0, 0x07, 0, pwm_ast),
-       PWRITE(pwm2_auto_spinup_time, 1, PRI_LOW, 0x5d, 0, 0x07, 0, pwm_ast),
-       PWRITE(pwm3_auto_spinup_time, 2, PRI_LOW, 0x5e, 0, 0x07, 0, pwm_ast),
-
-       PWRITE(peci_enable, 0, PRI_LOW, 0x40, 0, 0x01, 4, bitmask),
-       PWRITE(peci_avg, 0, PRI_LOW, 0x36, 0, 0x07, 0, bitmask),
-       PWRITE(peci_domain, 0, PRI_LOW, 0x36, 0, 0x01, 3, bitmask),
-       PWRITE(peci_legacy, 0, PRI_LOW, 0x36, 0, 0x01, 4, bitmask),
-       PWRITE(peci_diode, 0, PRI_LOW, 0x0e, 0, 0x07, 4, bitmask),
-       PWRITE(peci_4domain, 0, PRI_LOW, 0x0e, 0, 0x01, 4, bitmask),
+       PWRITE(pwm1_auto_point1_pwm, 0, 0x64, 0, 0, 0, u8),
+       PWRITE(pwm2_auto_point1_pwm, 1, 0x65, 0, 0, 0, u8),
+       PWRITE(pwm3_auto_point1_pwm, 2, 0x66, 0, 0, 0, u8),
+
+       PWRITE(pwm1_auto_point2_pwm, 0, 0x38, 0, 0, 0, u8),
+       PWRITE(pwm2_auto_point2_pwm, 1, 0x39, 0, 0, 0, u8),
+       PWRITE(pwm3_auto_point2_pwm, 2, 0x3a, 0, 0, 0, u8),
+
+       PWRITE(pwm1_freq, 0, 0x5f, 0, 0x0f, 0, pwm_freq),
+       PWRITE(pwm2_freq, 1, 0x60, 0, 0x0f, 0, pwm_freq),
+       PWRITE(pwm3_freq, 2, 0x61, 0, 0x0f, 0, pwm_freq),
+
+       PREAD(pwm1_auto_zone_assigned, 0, 0, 0, 0x03, 2, bitmask),
+       PREAD(pwm2_auto_zone_assigned, 1, 0, 0, 0x03, 4, bitmask),
+       PREAD(pwm3_auto_zone_assigned, 2, 0, 0, 0x03, 6, bitmask),
+
+       PWRITE(pwm1_auto_spinup_time, 0, 0x5c, 0, 0x07, 0, pwm_ast),
+       PWRITE(pwm2_auto_spinup_time, 1, 0x5d, 0, 0x07, 0, pwm_ast),
+       PWRITE(pwm3_auto_spinup_time, 2, 0x5e, 0, 0x07, 0, pwm_ast),
+
+       PWRITE(peci_enable, 0, 0x40, 0, 0x01, 4, bitmask),
+       PWRITE(peci_avg, 0, 0x36, 0, 0x07, 0, bitmask),
+       PWRITE(peci_domain, 0, 0x36, 0, 0x01, 3, bitmask),
+       PWRITE(peci_legacy, 0, 0x36, 0, 0x01, 4, bitmask),
+       PWRITE(peci_diode, 0, 0x0e, 0, 0x07, 4, bitmask),
+       PWRITE(peci_4domain, 0, 0x0e, 0, 0x01, 4, bitmask),
+
+};
+
+/* List of registers to poll at high priority:
+   Note: This list has been ordered to take advantage of the read-locking
+   capabilities of the aSC7621 for two-byte registers.  These registers must
+   be accessed in two successive read operations, although it does not matter
+   if they are read low-byte or high-byte first. */
+static const u8 asc7621_reglist_high[] = {
+    0x10, 0x25,        /* temp1_input LSB, MSB */
+    0x15, 0x26,        /* temp2_input LSB, MSB */
+    0x16, 0x27,        /* temp3_input LSB, MSB */
+    0x17, 0x33,        /* temp4_input LSB, MSB */
+    0x13, 0x20,        /* in0_input LSB, MSB */
+    0x18, 0x21,        /* in1_input LSB, MSB */
+    0x11, 0x22,        /* in2_input LSB, MSB */
+    0x12, 0x23,        /* in3_input LSB, MSB */
+    0x14, 0x24,        /* in4_input LSB, MSB */
+    0x28, 0x29,        /* fan1_input LSB, MSB */
+    0x2a, 0x2b,        /* fan2_input LSB, MSB */
+    0x2c, 0x2d,        /* fan3_input LSB, MSB */
+    0x2e, 0x2f,        /* fan4_input LSB, MSB */
+    0xf6, 0xf7,        /* temp5_input LSB, MSB */
+    0xf8, 0xf9,        /* temp6_input LSB, MSB */
+    0xfa, 0xfb,        /* temp7_input LSB, MSB */
+    0xfc, 0xfd,        /* temp8_input LSB, MSB */
+    0x30, 0x31, 0x32,  /* pwm1-pwm3 duty */
+    0x41, 0x42, 0x43,  /* alarms */
+};
 
+/* List of registers to poll at low priority: */
+static const u8 asc7621_reglist_low[] = {
+    0x02,              /* temp1-temp4 source */
+    0x0e,              /* temp5-temp8 enable, peci ctrl */
+    0x1c, 0x1d,        /* remote1-remote2 offset */
+    0x34, 0x35,        /* temp4 min/max */
+    0x36,              /* peci config */
+    0x38, 0x39, 0x3a,  /* pwm1-pwm3 max duty */
+    0x3b, 0x3c, 0x3d,  /* temp4 pwm control */
+    0x40,              /* peci enable */
+    0x44, 0x45,        /* in0 min/max */
+    0x46, 0x47,        /* in1 min/max */
+    0x48, 0x49,        /* in2 min/max */
+    0x4a, 0x4b,        /* in3 min/max */
+    0x4c, 0x4d,        /* in4 min/max */
+    0x4e, 0x4f,        /* temp1 min/max */
+    0x50, 0x51,        /* temp2 min/max */
+    0x52, 0x53,        /* temp3 min/max */
+    0x54, 0x55,        /* fan1 min */
+    0x56, 0x57,        /* fan2 min */
+    0x58, 0x59,        /* fan3 min */
+    0x5a, 0x5b,        /* fan4 min */
+    0x5c, 0x5d, 0x5e,  /* pwm1-pwm3 control */
+    0x5f, 0x60, 0x61,  /* temp1-temp3 pwm control */
+    0x62, 0x63,        /* temp1-temp3 pwm control */
+    0x64, 0x65, 0x66,  /* pwm1-pwm3 minimum */
+    0x67, 0x68, 0x69,  /* temp1-temp3 pwm limits */
+    0x6a, 0x6b, 0x6c,  /* temp1-temp3 pwm limits */
+    0x6d, 0x6e,        /* temp1-temp4 hyst */
 };
 
 static struct asc7621_data *asc7621_update_device(struct device *dev)
@@ -1020,43 +1090,31 @@
        struct i2c_client *client = to_i2c_client(dev);
        struct asc7621_data *data = i2c_get_clientdata(client);
        int i;
-
-/*
- * The asc7621 chips guarantee consistent reads of multi-byte values
- * regardless of the order of the reads.  No special logic is needed
- * so we can just read the registers in whatever  order they appear
- * in the asc7621_params array.
- */
+       u8 adr;
 
        mutex_lock(&data->update_lock);
 
        /* Read all the high priority registers */
 
-       if (!data->valid ||
-           time_after(jiffies, data->last_high_reading + INTERVAL_HIGH)) {
-
-               for (i = 0; i < ARRAY_SIZE(asc7621_register_priorities); i++) {
-                       if (asc7621_register_priorities[i] == PRI_HIGH) {
-                               data->reg[i] =
-                                   i2c_smbus_read_byte_data(client, i) & 0xff;
-                       }
-               }
-               data->last_high_reading = jiffies;
-       };                      /* last_reading */
-
-       /* Read all the low priority registers. */
-
-       if (!data->valid ||
-           time_after(jiffies, data->last_high_reading + INTERVAL_LOW)) {
-
-               for (i = 0; i < ARRAY_SIZE(asc7621_params); i++) {
-                       if (asc7621_register_priorities[i] == PRI_LOW) {
-                               data->reg[i] =
-                                   i2c_smbus_read_byte_data(client, i) & 0xff;
-                       }
-               }
-               data->last_low_reading = jiffies;
-       };                      /* last_reading */
+        if (!data->valid ||
+            time_after(jiffies, data->last_high_reading + INTERVAL_HIGH)) {
+                for (i = 0; i < ARRAY_SIZE(asc7621_reglist_high); i++) {
+                        adr = asc7621_reglist_high[i];
+                        data->reg[adr] =
+                            i2c_smbus_read_byte_data(client, adr) & 0xff;
+                }
+                data->last_high_reading = jiffies;
+        }
+
+        if (!data->valid ||
+           time_after(jiffies, data->last_low_reading + INTERVAL_LOW)) {
+                for (i = 0; i < ARRAY_SIZE(asc7621_reglist_low); i++) {
+                       adr = asc7621_reglist_low[i];
+                       data->reg[adr] =
+                           i2c_smbus_read_byte_data(client, adr) & 0xff;
+                }
+                data->last_low_reading = jiffies;
+        };
 
        data->valid = 1;
 
@@ -1085,7 +1143,7 @@
 
 static void asc7621_init_client(struct i2c_client *client)
 {
-       int value, i, j;
+       int value;
 
        dev_dbg(&client->dev, "Initializing device\n");
 
@@ -1112,22 +1170,6 @@
        dev_dbg(&client->dev, "Setting READY to: 0x%02x\n", value);
        write_byte(0x40, value & 0xff);
 
-       dev_dbg(&client->dev, "Loading register arrays.\n");
-
-       /*
-        * Collect all the registers needed into a single array.
-        * This way, if a register isn't actually used for anything,
-        * we don't retrieve it.
-        */
-
-       for (i = 0; i < ARRAY_SIZE(asc7621_params); i++) {
-               for (j = 0; j < ARRAY_SIZE(asc7621_params[i].msb); j++)
-                       asc7621_register_priorities[asc7621_params[i].msb[j]] =
-                           asc7621_params[i].priority;
-               for (j = 0; j < ARRAY_SIZE(asc7621_params[i].lsb); j++)
-                       asc7621_register_priorities[asc7621_params[i].lsb[j]] =
-                           asc7621_params[i].priority;
-       }
 }
 
 static struct i2c_driver asc7621_driver;

[-- Attachment #3: Type: text/plain, Size: 153 bytes --]

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [lm-sensors] patch: asc7621 driver bug fixes
  2008-07-05 13:37 [lm-sensors] patch: asc7621 driver bug fixes Ken Milmore
  2008-07-06  6:15 ` Hans de Goede
  2008-07-06 16:49 ` Ken Milmore
@ 2008-07-06 18:03 ` George Joseph
  2008-07-06 22:18 ` George Joseph
                   ` (7 subsequent siblings)
  10 siblings, 0 replies; 12+ messages in thread
From: George Joseph @ 2008-07-06 18:03 UTC (permalink / raw)
  To: lm-sensors

I'm working my way through this today.  Stay tuned...

> -----Original Message-----
> From: Ken Milmore [mailto:ken@kenm.demon.co.uk]
> Sent: Sunday, July 06, 2008 10:49 AM
> To: Hans de Goede
> Cc: George Joseph; lm-sensors@lm-sensors.org
> Subject: Re: [lm-sensors] patch: asc7621 driver bug fixes
>
> Hans / George,
>
> Although things are shaping up nicely, I think there is still quite a bit left to do to completely
> review the driver.
>
> The aSC7621 appears to have a lot of bells and whistles for closed loop temperature control etc. and
> George has tried to support as much of this feature set as possible.  Unfortunately that means there's
> quite a lot to review - I haven't really looked at PWM control yet.
>
> In any case, a few more things have come to light:
>
> 1.  In my last posting I wasn't completely sure if my "patch #2" was a good idea or not.  Having
> looked at the Andigilog specs, I have become convinced that it will be necessary after all.  The main
> problem is with the two-byte registers.  To get the read-locking to work, the MSB and LSB must be read
> together, one after the other (although it is unimportant which one is read first!).  So it is
> necessary to sequence the reads so that the two-byte registers are handled correctly.  My patch will
> correct this problem, although as I have said, I think that other, cleaner solutions may be possible.
>
> 2.  The voltage scaling used in the driver appears to be slightly off from the nominal values given in
> the spec.  To get the scaling exactly right, it is necessary to use a "muldiv" scheme which I've
> included in the patch (see below).
>
> 3.  I fixed a problem in store_fan16() whereby it wasn't possible to set the minimum fan RPM to zero.
> The aSC7621 explicitly allows this, and of course it is useful to prevent spurious alarms for fans
> which are missing or stopped.
>
> The attached patch rolls up all my changes so far.  It applies against George's original patch of 29
> May.  It includes all the bug fixes from my last submission, the voltage scaling and fan fixes
> mentioned above and some tidying up of the high and low priority register lists.
>
>
> To sum up, by now I'm very happy with the following functions of the driver:
>
> * Reading of voltages, fans and temperatures.
>
> * Setting of alarm limits and display of alarms.
>
> The following areas still need to be reviewed and perhaps improved:
>
> * PWM control.  A few scary lookup tables in the code that I don't understand yet!
>
> * The miscellaneous configuration knobs (Input selection, PECI config etc).  I haven't tested these.
>
> * I think there is space for improvement in how the low-priority register list is handled.  A full
> minute between updates is probably too long.
>
> I hope this is of some use.  I will try to look at reviewing the remaining parts of the driver as time
> allows.
>
> George, I'd appreciate your feedback on my patches so far; I realise you might want to approach some
> of these changes differently to the way I've done them.
>
> Best wishes,
>
> Ken.
>
>
> Hans de Goede wrote:
> > Ken Milmore wrote:
> >> George,
> >>
> >> Here are some suggested bug fixes for the asc7621 driver source which
> >> you posted to lm_sensors on 29 May.
> >> (http://lists.lm-sensors.org/pipermail/lm-sensors/2008-May/023257.htm
> >> l)
> >>
> >
> > Thanks for doing this, I promised George to review this driver, but
> > sofar haven't gotten around to doing this. Any chance you could do a
> > complete review of it (or have you already done so, it sure looks that
> > way :)
> >
> > George, I assume you will post a new version of your patch soon with
> > these fixes incorperated?
> >
> > Regards,
> >
> > Hans
> >
>


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [lm-sensors] patch: asc7621 driver bug fixes
  2008-07-05 13:37 [lm-sensors] patch: asc7621 driver bug fixes Ken Milmore
                   ` (2 preceding siblings ...)
  2008-07-06 18:03 ` George Joseph
@ 2008-07-06 22:18 ` George Joseph
  2008-07-07  6:05 ` George Joseph
                   ` (6 subsequent siblings)
  10 siblings, 0 replies; 12+ messages in thread
From: George Joseph @ 2008-07-06 22:18 UTC (permalink / raw)
  To: lm-sensors

New patch will follow tomorrow ( I need to update my build environment).  Other comments in line...

> -----Original Message-----
> From: Ken Milmore [mailto:ken@kenm.demon.co.uk]
> Sent: Sunday, July 06, 2008 10:49 AM
> To: Hans de Goede
> Cc: George Joseph; lm-sensors@lm-sensors.org
> Subject: Re: [lm-sensors] patch: asc7621 driver bug fixes
>
> Hans / George,
>
> Although things are shaping up nicely, I think there is still quite a bit left to do to completely
> review the driver.
>
> The aSC7621 appears to have a lot of bells and whistles for closed loop temperature control etc. and
> George has tried to support as much of this feature set as possible.  Unfortunately that means there's
> quite a lot to review - I haven't really looked at PWM control yet.
>
> In any case, a few more things have come to light:
>
> 1.  In my last posting I wasn't completely sure if my "patch #2" was a good idea or not.  Having
> looked at the Andigilog specs, I have become convinced that it will be necessary after all.  The main
> problem is with the two-byte registers.  To get the read-locking to work, the MSB and LSB must be read
> together, one after the other (although it is unimportant which one is read first!).  So it is
> necessary to sequence the reads so that the two-byte registers are handled correctly.  My patch will
> correct this problem, although as I have said, I think that other, cleaner solutions may be possible.

Yep, my goof.  I thought I had this accounted for but it must have been in a earlier version of the driver.  I solved it in the client_init code without creating a static array.  It only gets run once at module load and it solves the msb-lsb issue.

>
> 2.  The voltage scaling used in the driver appears to be slightly off from the nominal values given in
> the spec.  To get the scaling exactly right, it is necessary to use a "muldiv" scheme which I've
> included in the patch (see below).

Yep, fixed.

>
> 3.  I fixed a problem in store_fan16() whereby it wasn't possible to set the minimum fan RPM to zero.
> The aSC7621 explicitly allows this, and of course it is useful to prevent spurious alarms for fans
> which are missing or stopped.

Yep, fixed.

>
> The attached patch rolls up all my changes so far.  It applies against George's original patch of 29
> May.  It includes all the bug fixes from my last submission, the voltage scaling and fan fixes
> mentioned above and some tidying up of the high and low priority register lists.

All patches applied except for the register priorities and ordering which I fixed keeping the existing paradigm.

>
>
> To sum up, by now I'm very happy with the following functions of the driver:
>
> * Reading of voltages, fans and temperatures.
>
> * Setting of alarm limits and display of alarms.
>
> The following areas still need to be reviewed and perhaps improved:
>
> * PWM control.  A few scary lookup tables in the code that I don't understand yet!

Yeah, unfortunately, I can't think of a better way to translate what the chip exposes to the sysfs specs.  Input definitely welcome.

>
> * The miscellaneous configuration knobs (Input selection, PECI config etc).  I haven't tested these.

Actually, pay particular attention to PECI.  On my Conroes and Wolfdales, one of the peci temps (temp5_input) appears to be the elusive and undocumented Tjmax. :)

>
> * I think there is space for improvement in how the low-priority register list is handled.  A full
> minute between updates is probably too long.

Easy enough to change.  I was just copying from the other drivers.  Let me know what makes sense.

>
> I hope this is of some use.  I will try to look at reviewing the remaining parts of the driver as time
> allows.

Absolutely.  Keep sending feedback.

>
> George, I'd appreciate your feedback on my patches so far; I realise you might want to approach some
> of these changes differently to the way I've done them.

Patches are great and I appreciate the time you're putting into the review!!

george


>
> Best wishes,
>
> Ken.
>
>
> Hans de Goede wrote:
> > Ken Milmore wrote:
> >> George,
> >>
> >> Here are some suggested bug fixes for the asc7621 driver source which
> >> you posted to lm_sensors on 29 May.
> >> (http://lists.lm-sensors.org/pipermail/lm-sensors/2008-May/023257.htm
> >> l)
> >>
> >
> > Thanks for doing this, I promised George to review this driver, but
> > sofar haven't gotten around to doing this. Any chance you could do a
> > complete review of it (or have you already done so, it sure looks that
> > way :)
> >
> > George, I assume you will post a new version of your patch soon with
> > these fixes incorperated?
> >
> > Regards,
> >
> > Hans
> >
>


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [lm-sensors] patch: asc7621 driver bug fixes
  2008-07-05 13:37 [lm-sensors] patch: asc7621 driver bug fixes Ken Milmore
                   ` (3 preceding siblings ...)
  2008-07-06 22:18 ` George Joseph
@ 2008-07-07  6:05 ` George Joseph
  2008-07-07 23:05 ` Ken Milmore
                   ` (5 subsequent siblings)
  10 siblings, 0 replies; 12+ messages in thread
From: George Joseph @ 2008-07-07  6:05 UTC (permalink / raw)
  To: lm-sensors

[-- Attachment #1: Type: text/plain, Size: 5934 bytes --]

Here's the patch against 2.6.25.10.

signed-off-by: George Joseph <George.joseph@fairview5.com>

Ken, look at the new implementation of the priority registers.  It's still a loop initialization but it does get run only once at driver load.  With 98 registers of interest, I'm concerned that creating additional static arrays will make it hard to figure out what's going on.

george


> -----Original Message-----
> From: lm-sensors-bounces@lm-sensors.org [mailto:lm-sensors-bounces@lm-sensors.org] On Behalf Of George
> Joseph
> Sent: Sunday, July 06, 2008 4:18 PM
> To: Ken Milmore; Hans de Goede
> Cc: lm-sensors@lm-sensors.org
> Subject: Re: [lm-sensors] patch: asc7621 driver bug fixes
>
> New patch will follow tomorrow ( I need to update my build environment).  Other comments in line...
>
> > -----Original Message-----
> > From: Ken Milmore [mailto:ken@kenm.demon.co.uk]
> > Sent: Sunday, July 06, 2008 10:49 AM
> > To: Hans de Goede
> > Cc: George Joseph; lm-sensors@lm-sensors.org
> > Subject: Re: [lm-sensors] patch: asc7621 driver bug fixes
> >
> > Hans / George,
> >
> > Although things are shaping up nicely, I think there is still quite a bit left to do to completely
> > review the driver.
> >
> > The aSC7621 appears to have a lot of bells and whistles for closed loop temperature control etc. and
> > George has tried to support as much of this feature set as possible.  Unfortunately that means
> there's
> > quite a lot to review - I haven't really looked at PWM control yet.
> >
> > In any case, a few more things have come to light:
> >
> > 1.  In my last posting I wasn't completely sure if my "patch #2" was a good idea or not.  Having
> > looked at the Andigilog specs, I have become convinced that it will be necessary after all.  The
> main
> > problem is with the two-byte registers.  To get the read-locking to work, the MSB and LSB must be
> read
> > together, one after the other (although it is unimportant which one is read first!).  So it is
> > necessary to sequence the reads so that the two-byte registers are handled correctly.  My patch will
> > correct this problem, although as I have said, I think that other, cleaner solutions may be
> possible.
>
> Yep, my goof.  I thought I had this accounted for but it must have been in a earlier version of the
> driver.  I solved it in the client_init code without creating a static array.  It only gets run once
> at module load and it solves the msb-lsb issue.
>
> >
> > 2.  The voltage scaling used in the driver appears to be slightly off from the nominal values given
> in
> > the spec.  To get the scaling exactly right, it is necessary to use a "muldiv" scheme which I've
> > included in the patch (see below).
>
> Yep, fixed.
>
> >
> > 3.  I fixed a problem in store_fan16() whereby it wasn't possible to set the minimum fan RPM to
> zero.
> > The aSC7621 explicitly allows this, and of course it is useful to prevent spurious alarms for fans
> > which are missing or stopped.
>
> Yep, fixed.
>
> >
> > The attached patch rolls up all my changes so far.  It applies against George's original patch of 29
> > May.  It includes all the bug fixes from my last submission, the voltage scaling and fan fixes
> > mentioned above and some tidying up of the high and low priority register lists.
>
> All patches applied except for the register priorities and ordering which I fixed keeping the existing
> paradigm.
>
> >
> >
> > To sum up, by now I'm very happy with the following functions of the driver:
> >
> > * Reading of voltages, fans and temperatures.
> >
> > * Setting of alarm limits and display of alarms.
> >
> > The following areas still need to be reviewed and perhaps improved:
> >
> > * PWM control.  A few scary lookup tables in the code that I don't understand yet!
>
> Yeah, unfortunately, I can't think of a better way to translate what the chip exposes to the sysfs
> specs.  Input definitely welcome.
>
> >
> > * The miscellaneous configuration knobs (Input selection, PECI config etc).  I haven't tested these.
>
> Actually, pay particular attention to PECI.  On my Conroes and Wolfdales, one of the peci temps
> (temp5_input) appears to be the elusive and undocumented Tjmax. :)
>
> >
> > * I think there is space for improvement in how the low-priority register list is handled.  A full
> > minute between updates is probably too long.
>
> Easy enough to change.  I was just copying from the other drivers.  Let me know what makes sense.
>
> >
> > I hope this is of some use.  I will try to look at reviewing the remaining parts of the driver as
> time
> > allows.
>
> Absolutely.  Keep sending feedback.
>
> >
> > George, I'd appreciate your feedback on my patches so far; I realise you might want to approach some
> > of these changes differently to the way I've done them.
>
> Patches are great and I appreciate the time you're putting into the review!!
>
> george
>
>
> >
> > Best wishes,
> >
> > Ken.
> >
> >
> > Hans de Goede wrote:
> > > Ken Milmore wrote:
> > >> George,
> > >>
> > >> Here are some suggested bug fixes for the asc7621 driver source which
> > >> you posted to lm_sensors on 29 May.
> > >> (http://lists.lm-sensors.org/pipermail/lm-sensors/2008-May/023257.htm
> > >> l)
> > >>
> > >
> > > Thanks for doing this, I promised George to review this driver, but
> > > sofar haven't gotten around to doing this. Any chance you could do a
> > > complete review of it (or have you already done so, it sure looks that
> > > way :)
> > >
> > > George, I assume you will post a new version of your patch soon with
> > > these fixes incorperated?
> > >
> > > Regards,
> > >
> > > Hans
> > >
> >
>
>
> _______________________________________________
> lm-sensors mailing list
> lm-sensors@lm-sensors.org
> http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

[-- Attachment #2: asc7621_2.patch --]
[-- Type: application/octet-stream, Size: 52582 bytes --]

diff -urpN linux-2.6.25.10/Documentation/hwmon/asc7621 linux-2.6.25.10-gtj/Documentation/hwmon/asc7621
--- linux-2.6.25.10/Documentation/hwmon/asc7621	1969-12-31 17:00:00.000000000 -0700
+++ linux-2.6.25.10-gtj/Documentation/hwmon/asc7621	2008-07-06 18:39:06.202473470 -0600
@@ -0,0 +1,297 @@
+Kernel driver asc7621
+==================
+
+Supported chips:
+  * Andigilog aSC7621, aSC7621a
+    Prefix: 'asc7621'
+    Addresses scanned: I2C 0x2c, 0x2d, 0x2e
+    Datasheet: http://www.andigilog.com/downloads/aSC7621_70A06010.pdf
+
+Author:
+		George Joseph
+		
+		
+Description provided by Dave Pivin @ Andigilog:		
+
+Andigilog has both the PECI and pre-PECI versions of the Heceta-6, as
+Intel calls them. Heceta-6e has high frequency PWM and Heceta-6p has
+added PECI and a 4th thermal zone. The Andigilog aSC7611 is the
+Heceta-6e part and aSC7621 is the Heceta-6p part. They are both in
+volume production, shipping to Intel and their subs.
+
+We have enhanced both parts relative to the governing Intel
+specification. First enhancement is temperature reading resolution. We
+have used registers below 20h for vendor-specific functions in addition
+to those in the Intel-specified vendor range.
+
+Our conversion process produces a result that is reported as two bytes.
+The fan speed control uses this finer value to produce a "step-less" fan
+PWM output. These two bytes are "read-locked" to guarantee that once a
+high or low byte is read, the other byte is locked-in until after the
+next read of any register. So to get an atomic reading, read high or low
+byte, then the very next read should be the opposite byte. Our data
+sheet says 10-bits of resolution, although you may find the lower bits
+are active, they are not necessarily reliable or useful externally. We
+chose not to mask them.
+
+We employ significant filtering that is user tunable as described in the
+data sheet. Our temperature reports and fan PWM outputs are very smooth
+when compared to the competition, in addition to the higher resolution
+temperature reports. The smoother PWM output does not require user
+intervention.
+
+We offer GPIO features on the former VID pins. These are open-drain
+outputs or inputs and may be used as general purpose I/O or as alarm
+outputs that are based on temperature limits. These are in 19h and 1Ah.
+
+We offer flexible mapping of temperature readings to thermal zones. Any
+temperature may be mapped to any zone, which has a default assignment
+that follows Intel's specs. 
+
+Since there is a fan to zone assignment that allows for the "hotter" of
+a set of zones to control the PWM of an individual fan, but there is no
+indication to the user, we have added an indicator that shows which zone
+is currently controlling the PWM for a given fan. This is in register
+00h.
+
+Both remote diode temperature readings may be given an offset value such
+that the reported reading as well as the temperature used to determine
+PWM may be offset for system calibration purposes.
+
+PECI Extended configuration allows for having more than two domains per
+PECI address and also provides an enabling function for each PECI
+address. One could use our flexible zone assignment to have a zone
+assigned to up to 4 PECI addresses. This is not possible in the default
+Intel configuration. This would be useful in multi-CPU systems with
+individual fans on each that would benefit from individual fan control.
+This is in register 0Eh.
+
+The tachometer measurement system is flexible and able to adapt to many
+fan types. We can also support pulse-stretched PWM so that 3-wire fans
+may be used. These characteristics are in registers 04h to 07h.
+
+Finally, we have added a tach disable function that turns off the tach
+measurement system for individual tachs in order to save power. That is
+in register 75h.
+
+--
+aSC7621 Product Description
+
+The aSC7621 has a two wire digital interface compatible with SMBus 2.0. 
+Using a 10-bit ADC, the aSC7621 measures the temperature of two remote diode 
+connected transistors as well as its own die. Support for Platform 
+Environmental Control Interface (PECI) is included.  
+
+Using temperature information from these four zones, an automatic fan speed 
+control algorithm is employed to minimize acoustic impact while achieving 
+recommended CPU temperature under varying operational loads. 
+
+To set fan speed, the aSC7621 has three independent pulse width modulation 
+(PWM) outputs that are controlled by one, or a combination of three, 
+temperature zones. Both high- and low-frequency PWM ranges are supported. 
+
+The aSC7621 also includes a digital filter that can be invoked to smooth 
+temperature readings for better control of fan speed and minimum acoustic 
+impact. 
+
+The aSC7621 has tachometer inputs to measure fan speed on up to four fans. 
+Limit and status registers for all measured values are included to alert 
+the system host that any measurements are outside of programmed limits 
+via status registers.
+
+System voltages of VCCP, 2.5V, 3.3V, 5.0V, and 12V motherboard power are 
+monitored efficiently with internal scaling resistors.
+
+Features
+- Supports PECI interface and monitors internal and remote thermal diodes
+- 2-wire, SMBus 2.0 compliant, serial interface
+- 10-bit ADC
+- Monitors VCCP, 2.5V, 3.3V, 5.0V, and 12V motherboard/processor supplies
+- Programmable autonomous fan control based on temperature readings
+- Noise filtering of temperature reading for fan speed control
+- 0.25�C digital temperature sensor resolution
+- 3 PWM fan speed control outputs for 2-, 3- or 4-wire fans and up to 4 fan 
+	tachometer inputs
+- Enhanced measured temperature to Temperature Zone assignment.
+- Provides high and low PWM frequency ranges
+- 3 GPIO pins for custom use
+- 24-Lead QSOP package
+
+Configuration Notes
+===================
+
+Except where noted below, the sysfs entries created by this driver follow
+the standards defined in "sysfs-interface".
+
+temp1_source
+	0 	(default) peci_legacy = 0, Remote 1 Temperature 	 
+			peci_legacy = 1, PECI Processor Temperature 0 	 
+	1 	Remote 1 Temperature 	 
+	2 	Remote 2 Temperature 	 
+	3 	Internal Temperature 	 
+	4 	PECI Processor Temperature 0 	 
+	5 	PECI Processor Temperature 1 	 
+	6 	PECI Processor Temperature 2 	 
+	7   PECI Processor Temperature 3 	 
+
+temp2_source
+	0 	(default) Internal Temperature 	 
+	1 	Remote 1 Temperature 	 
+	2 	Remote 2 Temperature 	 
+	3 	Internal Temperature 	 
+	4 	PECI Processor Temperature 0 	 
+	5 	PECI Processor Temperature 1 	 
+	6 	PECI Processor Temperature 2 	 
+	7 	PECI Processor Temperature 3 	 
+
+temp3_source
+	0 	(default) Remote 2 Temperature
+	1 	Remote 1 Temperature
+	2 	Remote 2 Temperature
+	3 	Internal Temperature
+	4 	PECI Processor Temperature 0
+	5 	PECI Processor Temperature 1
+	6 	PECI Processor Temperature 2
+	7 	PECI Processor Temperature 3
+
+temp4_source
+	0 	(default) peci_legacy = 0, PECI Processor Temperature 0
+			peci_legacy = 1, Remote 1 Temperature
+	1 	Remote 1 Temperature
+	2 	Remote 2 Temperature
+	3 	Internal Temperature
+	4 	PECI Processor Temperature 0
+	5 	PECI Processor Temperature 1
+	6 	PECI Processor Temperature 2
+	7 	PECI Processor Temperature 3
+
+temp[1-4]_smoothing_enable
+temp[1-4]_smoothing_time
+	Smooths spikes in temp readings caused by noise.
+	0	35 sec
+	1	17.6 sec
+	2	11.8 sec
+	3	7.0 sec
+	4	4.4 sec
+	5	3.0 sec
+	6	1.6 sec
+	7	0.8 sec
+
+temp[1-4]_crit
+	When the corresponding zone temperature reaches this value,
+	ALL pwm outputs will got to 100%.
+
+temp[5-8]_input
+temp[5-8]_enable
+	The aSC7621 can also read temperatures provided by the processor
+	via the PECI bus.  Usually these are "core" temps and are relative
+	to the point where the automatical thermal control circuit starts
+	throttling.  This means that these are usually negative numbers.
+
+pwm[1-3]_enable
+	0		Fan off.
+	1		Fan on manual control.
+	2		Fan on automatic control and will run at the minimum pwm
+				if the temperature for the zone is below the minimum.
+	3		Fan on automatic control but will be off if the temperature 
+				for the zone is below the minimum.
+	4-254	Ignored.
+	255		Fan on full.
+	
+pwm[1-3]_auto_channels
+	Bitmap as described in sysctl-interface with the following
+	exceptions...
+	Only the following combinations of zones (and their corresponding masks) 
+	are valid:
+	1			
+	2
+	3
+	2,3
+	1,2,3
+	4
+	1,2,3,4
+
+	Special values:
+	0		Disabled.
+	16		Fan on manual control.
+	31		Fan on full.
+	
+	
+pwm[1-3]_invert
+	When set, inverts the meaning of pwm[1-3].  
+	I.E.  when pwm = 0, the fan will be on full and
+	when pwm = 255 the fan will be off.
+	
+pwm[1-3]_freq
+	PWM frequency in Hz
+	Valid values in Hz are:
+	
+	10
+	15
+	23
+	30  (default)
+	38
+	47
+	62
+	94
+	23000
+	24000
+	25000
+	26000
+	27000
+	28000
+	29000
+	30000
+	
+	Setting any other value will be ignored.
+	
+peci_enable
+	Enables or disables PECI
+	
+peci_avg
+	Input filter averate time.
+	
+	0 	0 Sec. (no Smoothing) (default) 
+	1 	0.25 Sec.
+	2 	0.5 Sec.
+	3 	1.0 Sec.
+	4 	2.0 Sec.
+	5 	4.0 Sec.
+	6 	8.0 Sec.
+	7 	0.0 Sec	
+
+peci_legacy
+
+	0 	Standard Mode (default)
+		Remote Diode 1 reading is associated with
+		Temperature Zone 1, PECI is associated with
+		Zone 4
+	
+	1	Legacy Mode
+		PECI is associated with Temperature Zone 1,
+		Remote Diode 1 is associated with Zone 4
+
+peci_diode
+	Diode filter
+	
+	0	0.25 Sec.
+	1 	1.1 Sec.
+	2 	2.4 Sec.  (default) 
+	3 	3.4 Sec.
+	4 	5.0 Sec.
+	5 	6.8 Sec.
+	6 	10.2 Sec.
+	7 	16.4 Sec.	
+
+peci_4domain
+	Four domain enable
+	
+	0 	1 or 2 Domains for enabled processors (default) 
+	1 	3 or 4 Domains for enabled processors
+
+peci_domain
+	Domain
+	
+	0 	Processor contains a single domain (0) 	 (default)	
+	1 	Processor contains two domains (0,1)
+	
diff -urpN linux-2.6.25.10/drivers/hwmon/asc7621.c linux-2.6.25.10-gtj/drivers/hwmon/asc7621.c
--- linux-2.6.25.10/drivers/hwmon/asc7621.c	1969-12-31 17:00:00.000000000 -0700
+++ linux-2.6.25.10-gtj/drivers/hwmon/asc7621.c	2008-07-06 18:39:43.016588076 -0600
@@ -0,0 +1,1365 @@
+/*
+    asc7621.c - Part of lm_sensors, Linux kernel modules for hardware
+             monitoring
+    Copyright (c) 2007 George Joseph  <george.joseph@fairview5.com>
+
+    Chip details at <http://www.andigilog.com>
+
+ * 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 program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301 USA.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/jiffies.h>
+#include <linux/i2c.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+
+/* Addresses to scan */
+static unsigned short normal_i2c[] = {
+	0x2c, 0x2d, 0x2e, I2C_CLIENT_END
+};
+
+/* 
+ * Insmod parameters
+ * This macro also creates the "chips" enum referenced later.
+ */
+I2C_CLIENT_INSMOD_2(asc7621, asc7621a);
+
+#define INTERVAL_HIGH  (HZ + HZ / 2)
+#define INTERVAL_LOW  (1 * 60 * HZ)
+#define PRI_NONE 0
+#define PRI_LOW 1
+#define PRI_HIGH 2
+#define FIRST_CHIP asc7621
+#define LAST_CHIP asc7621a
+
+struct asc7621_chip {
+	char *name;
+	enum chips chip_type;
+	u8 company_reg;
+	u8 company_id;
+	u8 verstep_reg;
+	u8 verstep_id;
+	unsigned short *addresses;
+};
+/*
+ * The "chips" enum created by I2C_CLIENT_INSMOD_2 has "any_chip" as
+ * the first element in the enum, so it must also be first in our array.
+ */
+static struct asc7621_chip asc7621_chips[] = {
+	{
+	 .name = "any",.chip_type = any_chip,
+	 },
+	{
+	 .name = "asc7621",.chip_type = asc7621,
+	 .company_reg = 0x3e,.company_id = 0x61,
+	 .verstep_reg = 0x3f,.verstep_id = 0x6c,
+	 .addresses = normal_i2c,
+	 },
+	{
+	 .name = "asc7621a",.chip_type = asc7621a,
+	 .company_reg = 0x3e,.company_id = 0x61,
+	 .verstep_reg = 0x3f,.verstep_id = 0x6d,
+	 .addresses = normal_i2c,
+	 },
+};
+
+/* 
+ * Defines the highest register to be used, not the count.
+ * The actual count will probably be smaller because of gaps
+ * in the implementation (unused register locations).
+ * This define will safely set the array size of both the parameter
+ * and data arrays.
+ * This comes from the data sheet register description table.
+ */
+#define LAST_REGISTER 0xff
+
+struct asc7621_data {
+	struct i2c_client client;
+	struct device *class_dev;
+	struct mutex update_lock;
+	int valid;		/* !=0 if following fields are valid */
+	unsigned long last_high_reading;	/* In jiffies */
+	unsigned long last_low_reading;	/* In jiffies */
+
+	/* 
+	 * Registers we care about occupy the corresponding index
+	 * in the array.  Registers we don't care about are left
+	 * at 0.
+	 */
+	u8 reg[LAST_REGISTER + 1];
+};
+
+/* 
+ * Macro to get the parent asc7621_param structure
+ * from a sensor_device_attribute passed into the
+ * show/store functions.
+ */
+#define to_asc7621_param(_sda) \
+	container_of(_sda, struct asc7621_param, sda)
+
+/*
+ * Each parameter to be retrieved needs an asc7621_param structure
+ * allocated.  It contains the sensor_device_attribute structure
+ * and the control info needed to retrieve the value from the register map.
+*/
+struct asc7621_param {
+	struct sensor_device_attribute sda;
+	u8 priority;
+	u8 msb[3];
+	u8 lsb[3];
+	u8 mask[3];
+	u8 shift[3];
+	u8 value;
+	char *label;
+};
+
+/*
+ * This is the map that ultimately indicates whether we'll be
+ * retrieving a register value or not, and at what frequency.
+ */
+static int last_low_priority;
+static u8 asc7621_register_low_priorities[255];
+static int last_high_priority;
+static u8 asc7621_register_high_priorities[255];
+
+static struct asc7621_data *asc7621_update_device(struct device *dev);
+
+#define read_byte(reg) (i2c_smbus_read_byte_data(client, reg) & 0xff)
+#define write_byte(reg, data) i2c_smbus_write_byte_data(client, reg, data)
+
+/*******************************************************
+ *         Data Handlers
+ * Each function handles the formatting, storage
+ * and retrieval of like parameters.
+ *
+ *******************************************************/
+
+#define SETUP_SHOW_data_param(d, a) \
+	struct sensor_device_attribute *sda = to_sensor_dev_attr(a); \
+	struct asc7621_data *data = asc7621_update_device(d); \
+	struct asc7621_param *param = to_asc7621_param(sda)
+
+#define SETUP_STORE_data_param(d, a) \
+	struct sensor_device_attribute *sda = to_sensor_dev_attr(a); \
+	struct i2c_client *client = to_i2c_client(d); \
+	struct asc7621_data *data = i2c_get_clientdata(client); \
+	struct asc7621_param *param = to_asc7621_param(sda)
+
+/*
+ * u8 is just what it sounds like...an unsigned byte with no
+ * special formatting.
+ */
+static ssize_t show_u8(struct device *dev, struct device_attribute *attr,
+		       char *buf)
+{
+	SETUP_SHOW_data_param(dev, attr);
+
+	return sprintf(buf, "%u\n", data->reg[param->msb[0]]);
+}
+
+static ssize_t store_u8(struct device *dev, struct device_attribute *attr,
+			const char *buf, size_t count)
+{
+	SETUP_STORE_data_param(dev, attr);
+
+	u8 reqval = simple_strtoul(buf, NULL, 10);
+
+	mutex_lock(&data->update_lock);
+	data->reg[param->msb[0]] = reqval;
+	write_byte(param->msb[0], reqval);
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+/* 
+ * Many of the config values occupy only a few bits of a register.
+ */
+static ssize_t show_bitmask(struct device *dev,
+			    struct device_attribute *attr, char *buf)
+{
+	SETUP_SHOW_data_param(dev, attr);
+
+	return sprintf(buf, "%u\n",
+		       (data->reg[param->msb[0]] >> param->
+			shift[0]) & param->mask[0]);
+}
+
+static ssize_t store_bitmask(struct device *dev,
+			     struct device_attribute *attr,
+			     const char *buf, size_t count)
+{
+	SETUP_STORE_data_param(dev, attr);
+
+	u8 reqval = simple_strtoul(buf, NULL, 10);
+	u8 currval;
+
+	reqval = (reqval & param->mask[0]) << param->shift[0];
+
+	mutex_lock(&data->update_lock);
+	currval = read_byte(param->msb[0]);
+	reqval |= (currval & ~(param->mask[0] << param->shift[0]));
+	data->reg[param->msb[0]] = reqval;
+	write_byte(param->msb[0], reqval);
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+/* 16 bit fan rpm values 
+ *
+ * reported by the device as the number of 11.111us periods (90khz)
+ * between full fan rotations.  Therefore...
+ *
+ * RPM = (90000 * 60) / register value
+ *
+ */
+static ssize_t show_fan16(struct device *dev,
+			  struct device_attribute *attr, char *buf)
+{
+	SETUP_SHOW_data_param(dev, attr);
+
+	u16 regval = (data->reg[param->msb[0]] << 8) | data->reg[param->lsb[0]];
+
+	return sprintf(buf, "%u\n",
+		       (regval == 0 ? -1 : (regval) ==
+			0xffff ? 0 : 5400000 / regval));
+}
+
+static ssize_t store_fan16(struct device *dev,
+			   struct device_attribute *attr, const char *buf,
+			   size_t count)
+{
+	SETUP_STORE_data_param(dev, attr);
+
+	u32 reqval = simple_strtoul(buf, NULL, 10);
+
+	/* If a minimum RPM of zero is requested, then we set the register to
+	   0xffff. This value allows the fan to be stopped completely without
+	   generating an alarm. If any value other than 0 is requested, then
+	   we limit the calculated tacho period to 0xfffe, which equates to a
+	   minimum rotation speed of about 82 RPM. */
+	reqval = (reqval == 0 ? 0xffff :
+		  SENSORS_LIMIT(5400000 / reqval, 0, 0xfffe));
+
+	mutex_lock(&data->update_lock);
+	data->reg[param->msb[0]] = (reqval >> 8) & 0xff;
+	data->reg[param->lsb[0]] = reqval & 0xff;
+	write_byte(param->msb[0], data->reg[param->msb[0]]);
+	write_byte(param->lsb[0], data->reg[param->lsb[0]]);
+	mutex_unlock(&data->update_lock);
+
+	return count;
+}
+
+/* Voltages are scaled in the device so that the nominal voltage is
+ * 3/4ths of the 0-255 range (I.E 192 in the MSB).
+ * If all voltages are 'normal' then all voltage registers will read
+ * 0xC0 in the MSB.
+ *
+ * The tables below scale the voltages readings so that the 3/4 scale
+ * reading maps exactly to the Nominal Voltage for each sensor given in
+ * the datasheet, as follows:
+ *
+ * in0 (2.5V)  : 192 * 625 / 48 =  2500
+ * in1 (2.25V) : 192 * 375 / 32 =  2250
+ * in2 (3.3V)  : 192 * 275 / 16 =  3300
+ * in4 (5V)    : 192 * 625 / 24 =  5000
+ * in5 (12V)   : 192 * 125 /  2 = 12000
+ *
+ * To scale the 10-bit registers, we multiply the divisors by 4.
+ * When writing the limit registers, we interchange multipliers and divisors.
+*/
+
+static s32 asc7621_in_scaling_mul[] = { 625, 375, 275, 625, 125 };
+static s32 asc7621_in_scaling_div[] = { 48, 32, 16, 24, 2 };
+
+static ssize_t show_in10(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	u8 nr;
+	s32 regval;
+	SETUP_SHOW_data_param(dev, attr);
+
+	nr = sda->index;
+
+	/* The LSB value is a 2-bit scaling of the MSB's LSbit value. */
+	regval = (data->reg[param->msb[0]] << 2) +
+	    (data->reg[param->lsb[0]] >> 6);
+	regval *= asc7621_in_scaling_mul[nr];
+	regval /= (asc7621_in_scaling_div[nr] * 4);
+
+	return sprintf(buf, "%u\n", regval);
+}
+
+/* 8 bit voltage values (the mins and maxs) */
+static ssize_t show_in8(struct device *dev, struct device_attribute *attr,
+			char *buf)
+{
+	u8 nr;
+	unsigned int regval;
+
+	SETUP_SHOW_data_param(dev, attr);
+
+	nr = sda->index;
+	regval = data->reg[param->msb[0]];
+	regval *= asc7621_in_scaling_mul[nr];
+	regval /= asc7621_in_scaling_div[nr];
+
+	return sprintf(buf, "%u\n", regval);
+}
+
+static ssize_t store_in8(struct device *dev, struct device_attribute *attr,
+			 const char *buf, size_t count)
+{
+
+	u8 nr;
+	s32 reqval;
+
+	SETUP_STORE_data_param(dev, attr);
+
+	nr = sda->index;
+	reqval = simple_strtoul(buf, NULL, 10);
+	reqval *= asc7621_in_scaling_div[nr];
+	reqval /= asc7621_in_scaling_mul[nr];
+	reqval = SENSORS_LIMIT(reqval, 0, 255);
+
+	mutex_lock(&data->update_lock);
+	data->reg[param->msb[0]] = reqval;
+	write_byte(param->msb[0], reqval);
+	mutex_unlock(&data->update_lock);
+
+	return count;
+}
+
+static ssize_t show_temp8(struct device *dev,
+			  struct device_attribute *attr, char *buf)
+{
+	SETUP_SHOW_data_param(dev, attr);
+
+	return sprintf(buf, "%d\n", ((s8) data->reg[param->msb[0]]) * 1000);
+}
+
+static ssize_t store_temp8(struct device *dev,
+			   struct device_attribute *attr, const char *buf,
+			   size_t count)
+{
+	SETUP_STORE_data_param(dev, attr);
+
+	s32 reqval = simple_strtol(buf, NULL, 10);
+	s8 temp = SENSORS_LIMIT((reqval / 1000), -127, 127);
+
+	mutex_lock(&data->update_lock);
+	data->reg[param->msb[0]] = temp;
+	write_byte(param->msb[0], temp);
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+/*
+ * Temperatures that occupy 2 bytes always have the whole
+ * number of degrees in the MSB with some part of the LSB
+ * indicating fractional degrees.
+ */
+
+/*   mmmmmmmm.llxxxxxx */
+static ssize_t show_temp10(struct device *dev,
+			   struct device_attribute *attr, char *buf)
+{
+	SETUP_SHOW_data_param(dev, attr);
+
+	u8 msb = data->reg[param->msb[0]];
+	u8 lsb = (data->reg[param->lsb[0]] >> 6) & 0x03;
+	int temp = (((s8) msb) * 1000) + (lsb * 250);
+
+	return sprintf(buf, "%d\n", temp);
+}
+
+/*   mmmmmm.ll */
+static ssize_t show_temp62(struct device *dev,
+			   struct device_attribute *attr, char *buf)
+{
+	SETUP_SHOW_data_param(dev, attr);
+
+	u8 regval = data->reg[param->msb[0]];
+	u32 temp = ((s8) (regval & 0xfc) * 1000) + ((regval & 0x03) * 250);
+
+	return sprintf(buf, "%d\n", temp);
+}
+
+static ssize_t store_temp62(struct device *dev,
+			    struct device_attribute *attr, const char *buf,
+			    size_t count)
+{
+	SETUP_STORE_data_param(dev, attr);
+
+	s32 i, f;
+	s8 temp;
+
+	s32 reqval = simple_strtol(buf, NULL, 10);
+	reqval = SENSORS_LIMIT(reqval, -32000, 31750);
+	i = reqval / 1000;
+	f = reqval - (i * 1000);
+	temp = i << 2;
+	temp |= f / 250;
+
+	mutex_lock(&data->update_lock);
+	data->reg[param->msb[0]] = temp;
+	write_byte(param->msb[0], temp);
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+/*
+ * The aSC7621 doesn't provide an "auto_point2".  Instead, you
+ * specify the auto_point1 and a range.  To keep with the sysfs
+ * hwmon specs, we synthesize the auto_point_2 from them.
+ */
+
+static u32 asc7621_range_map[] = {
+	2000, 2500, 3330, 4000, 5000, 6670, 8000, 10000,
+	13330, 16000, 20000, 26670, 32000, 40000, 53330, 80000,
+};
+
+static ssize_t show_ap2_temp(struct device *dev,
+			     struct device_attribute *attr, char *buf)
+{
+	SETUP_SHOW_data_param(dev, attr);
+
+	s32 auto_point1 = ((s8) data->reg[param->msb[1]]) * 1000;
+	u8 regval =
+	    ((data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0]);
+	s32 temp =
+	    auto_point1 + asc7621_range_map[SENSORS_LIMIT(regval, 0, 15)];
+
+	return sprintf(buf, "%d\n", temp);
+
+}
+
+static ssize_t store_ap2_temp(struct device *dev,
+			      struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	SETUP_STORE_data_param(dev, attr);
+
+	s32 reqval = simple_strtoul(buf, NULL, 10);
+	s32 auto_point1 = data->reg[param->msb[1]] * 1000;
+	s8 i = 0;
+	u8 currval = 0;
+	u8 newval = 255;
+
+	for (i = ARRAY_SIZE(asc7621_range_map) - 1; i >= 0; i--) {
+		if (reqval >= auto_point1 + asc7621_range_map[i]) {
+			newval = i;
+			break;
+		}
+	}
+	if (newval == 255)
+		return count;
+
+	newval = (newval & param->mask[0]) << param->shift[0];
+
+	mutex_lock(&data->update_lock);
+	currval = read_byte(param->msb[0]);
+	newval |= (currval & ~(param->mask[0] << param->shift[0]));
+	data->reg[param->msb[0]] = newval;
+	write_byte(param->msb[0], newval);
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+static ssize_t show_pwm_ac(struct device *dev,
+			   struct device_attribute *attr, char *buf)
+{
+	SETUP_SHOW_data_param(dev, attr);
+
+	u8 config =
+	    (data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0];
+	u8 altbit =
+	    (data->reg[param->msb[1]] >> param->shift[1]) & param->mask[1];
+	u8 regval = config | (altbit << 3);
+	u8 map[] = {
+		0x01, 0x02, 0x04, 0x1f, 0x00, 0x06, 0x07, 0x10,
+		0x08, 0x0f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f
+	};
+
+	return sprintf(buf, "%u\n", map[SENSORS_LIMIT(regval, 0, 15)]);
+}
+
+static ssize_t store_pwm_ac(struct device *dev,
+			    struct device_attribute *attr,
+			    const char *buf, size_t count)
+{
+	SETUP_STORE_data_param(dev, attr);
+
+	u8 reqval = simple_strtoul(buf, NULL, 10);
+	u8 currval = 0;
+	u8 config = 0;
+	u8 altbit = 0;
+	u8 newval = 0;
+	u16 map[] = {
+		0x04, 0x00, 0x01, 0xff, 0x02, 0xff, 0x05, 0x06,
+		0x08, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f,
+		0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03,
+	};
+
+	if (reqval > 31)
+		return count;
+
+	reqval = map[reqval];
+
+	config = reqval & 0x07;
+	altbit = (reqval >> 3) & 0x01;
+
+	config = (config & param->mask[0]) << param->shift[0];
+	altbit = (altbit & param->mask[1]) << param->shift[1];
+
+	mutex_lock(&data->update_lock);
+	currval = read_byte(param->msb[0]);
+	newval = config | (currval & ~(param->mask[0] << param->shift[0]));
+	newval = altbit | (newval & ~(param->mask[1] << param->shift[1]));
+	data->reg[param->msb[0]] = newval;
+	write_byte(param->msb[0], newval);
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+static ssize_t show_pwm_enable(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	SETUP_SHOW_data_param(dev, attr);
+
+	u8 config =
+	    (data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0];
+	u8 altbit =
+	    (data->reg[param->msb[1]] >> param->shift[1]) & param->mask[1];
+	u8 minoff =
+	    (data->reg[param->msb[2]] >> param->shift[2]) & param->mask[2];
+	u8 val = config | (altbit << 3);
+	u8 newval = 0;
+
+	if (val == 3 || val >= 10)
+		newval = 255;
+	else if (val == 4)
+		newval = 0;
+	else if (val == 7)
+		newval = 1;
+	else if (minoff == 1)
+		newval = 2;
+	else
+		newval = 3;
+
+	return sprintf(buf, "%u\n", newval);
+}
+
+static ssize_t store_pwm_enable(struct device *dev,
+				struct device_attribute *attr,
+				const char *buf, size_t count)
+{
+	SETUP_STORE_data_param(dev, attr);
+
+	u8 reqval = simple_strtoul(buf, NULL, 10);
+	u8 currval = 0;
+	u8 config = 0;
+	u8 altbit = 0;
+	u8 newval = 0;
+	u8 minoff = 255;
+
+	switch (reqval) {
+	case (0):
+		newval = 0x04;
+		break;
+	case (1):
+		newval = 0x07;
+		break;
+	case (2):
+		newval = 0x00;
+		minoff = 1;
+		break;
+	case (3):
+		newval = 0x00;
+		minoff = 0;
+		break;
+	case (255):
+		newval = 0x03;
+		break;
+	default:
+		return count;
+	}
+
+	config = newval & 0x07;
+	altbit = (newval >> 3) & 0x01;
+
+	mutex_lock(&data->update_lock);
+	config = (config & param->mask[0]) << param->shift[0];
+	altbit = (altbit & param->mask[1]) << param->shift[1];
+	currval = read_byte(param->msb[0]);
+	newval = config | (currval & ~(param->mask[0] << param->shift[0]));
+	newval = altbit | (newval & ~(param->mask[1] << param->shift[1]));
+	data->reg[param->msb[0]] = newval;
+	write_byte(param->msb[0], newval);
+	if (minoff < 255) {
+		minoff = (minoff & param->mask[2]) << param->shift[2];
+		currval = read_byte(param->msb[2]);
+		newval =
+		    minoff | (currval & ~(param->mask[2] << param->shift[2]));
+		data->reg[param->msb[2]] = newval;
+		write_byte(param->msb[2], newval);
+	}
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+static u32 asc7621_pwm_freq_map[] = {
+	10, 15, 23, 30, 38, 47, 62, 94,
+	23000, 24000, 25000, 26000, 27000, 28000, 29000, 30000
+};
+
+static ssize_t show_pwm_freq(struct device *dev,
+			     struct device_attribute *attr, char *buf)
+{
+	SETUP_SHOW_data_param(dev, attr);
+
+	u8 regval =
+	    (data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0];
+	regval = SENSORS_LIMIT(regval, 0, ARRAY_SIZE(asc7621_pwm_freq_map) - 1);
+
+	return sprintf(buf, "%u\n", asc7621_pwm_freq_map[regval]);
+}
+
+static ssize_t store_pwm_freq(struct device *dev,
+			      struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	SETUP_STORE_data_param(dev, attr);
+
+	u32 reqval = simple_strtoul(buf, NULL, 10);
+	u8 currval = 0;
+	u8 newval = 255;
+	u32 i;
+
+	for (i = 0; i < ARRAY_SIZE(asc7621_pwm_freq_map); i++) {
+		if (reqval == asc7621_pwm_freq_map[i]) {
+			newval = i;
+			break;
+		}
+	}
+	if (newval == 255)
+		return count;
+
+	newval = (newval & param->mask[0]) << param->shift[0];
+
+	mutex_lock(&data->update_lock);
+	currval = read_byte(param->msb[0]);
+	newval |= (currval & ~(param->mask[0] << param->shift[0]));
+	data->reg[param->msb[0]] = newval;
+	write_byte(param->msb[0], newval);
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+static u32 asc7621_pwm_auto_spinup_map[] =
+    { 0, 100, 250, 400, 700, 1000, 2000, 4000 };
+
+static ssize_t show_pwm_ast(struct device *dev,
+			    struct device_attribute *attr, char *buf)
+{
+	SETUP_SHOW_data_param(dev, attr);
+
+	u8 regval =
+	    (data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0];
+	regval =
+	    SENSORS_LIMIT(regval, 0,
+			  ARRAY_SIZE(asc7621_pwm_auto_spinup_map) - 1);
+
+	return sprintf(buf, "%u\n", asc7621_pwm_auto_spinup_map[regval]);
+
+}
+
+static ssize_t store_pwm_ast(struct device *dev,
+			     struct device_attribute *attr,
+			     const char *buf, size_t count)
+{
+	SETUP_STORE_data_param(dev, attr);
+
+	u16 reqval = simple_strtoul(buf, NULL, 10);
+	u8 currval = 0;
+	u8 newval = 255;
+	u32 i;
+
+	for (i = 0; i < ARRAY_SIZE(asc7621_pwm_auto_spinup_map); i++) {
+		if (reqval == asc7621_pwm_auto_spinup_map[i]) {
+			newval = i;
+			break;
+		}
+	}
+	if (newval == 255)
+		return count;
+
+	newval = (newval & param->mask[0]) << param->shift[0];
+
+	mutex_lock(&data->update_lock);
+	currval = read_byte(param->msb[0]);
+	newval |= (currval & ~(param->mask[0] << param->shift[0]));
+	data->reg[param->msb[0]] = newval;
+	write_byte(param->msb[0], newval);
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+static u32 asc7621_temp_smoothing_time_map[] =
+    { 35000, 17600, 11800, 7000, 4400, 3000, 1600, 800 };
+
+static ssize_t show_temp_st(struct device *dev,
+			    struct device_attribute *attr, char *buf)
+{
+	SETUP_SHOW_data_param(dev, attr);
+
+	u8 regval =
+	    (data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0];
+	regval =
+	    SENSORS_LIMIT(regval, 0,
+			  ARRAY_SIZE(asc7621_temp_smoothing_time_map) - 1);
+
+	return sprintf(buf, "%u\n", asc7621_temp_smoothing_time_map[regval]);
+}
+
+static ssize_t store_temp_st(struct device *dev,
+			     struct device_attribute *attr,
+			     const char *buf, size_t count)
+{
+	SETUP_STORE_data_param(dev, attr);
+
+	u16 reqval = simple_strtoul(buf, NULL, 10);
+	u8 currval;
+	u8 newval = 255;
+	u32 i;
+
+	for (i = 0; i < ARRAY_SIZE(asc7621_pwm_auto_spinup_map); i++) {
+		if (reqval == asc7621_temp_smoothing_time_map[i]) {
+			newval = i;
+			break;
+		}
+	}
+
+	if (newval == 255)
+		return count;
+
+	newval = (newval & param->mask[0]) << param->shift[0];
+
+	mutex_lock(&data->update_lock);
+	currval = read_byte(param->msb[0]);
+	newval |= (currval & ~(param->mask[0] << param->shift[0]));
+	data->reg[param->msb[0]] = newval;
+	write_byte(param->msb[0], newval);
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+static ssize_t show_const(struct device *dev,
+			  struct device_attribute *attr, char *buf)
+{
+	struct sensor_device_attribute *sda = to_sensor_dev_attr(attr);
+	struct asc7621_param *param = to_asc7621_param(sda);
+
+	return sprintf(buf, "%d\n", param->value);
+}
+
+static ssize_t show_label(struct device *dev,
+			  struct device_attribute *attr, char *buf)
+{
+	struct sensor_device_attribute *sda = to_sensor_dev_attr(attr);
+	struct asc7621_param *param = to_asc7621_param(sda);
+	if (param->label == NULL)
+		return (0);
+	return sprintf(buf, "%s\n", param->label);
+}
+
+/*   End of data handlers  */
+
+/*  These defines do nothing more than make the table easier
+ *  to read when wrapped at column 80.
+ */
+
+/*
+ * Creates a variable length array inititalizer.
+ * VAA(1,3,5,7) would produce {1,3,5,7}
+ */
+#define VAA(args...) {args}
+
+#define PREAD(name, n, pri, rm, rl, m, s, r) \
+	{.sda = SENSOR_ATTR(name, S_IRUGO, show_##r, NULL, n), \
+	  .priority = pri,.msb[0] = rm, .lsb[0] = rl, .mask[0] = m, \
+	  .shift[0] = s,}
+
+#define PWRITE(name, n, pri, rm, rl, m, s, r) \
+	{.sda = SENSOR_ATTR(name, S_IRUGO | S_IWUSR, show_##r, store_##r, n), \
+	  .priority = pri,.msb[0] = rm, .lsb[0] = rl, .mask[0] = m, \
+	  .shift[0] = s,}
+
+/*
+ * PWRITEM assumes that the initializers for the .msb, .lsb, .mask and .shift
+ * were created using the VAA macro.
+ */
+#define PWRITEM(name, n, pri, rm, rl, m, s, r) \
+	{.sda = SENSOR_ATTR(name, S_IRUGO | S_IWUSR, show_##r, store_##r, n), \
+	  .priority = pri,.msb = rm, .lsb = rl, .mask = m, .shift = s,}
+
+#define PCONST(name, n, v) \
+	{.sda = SENSOR_ATTR(name, S_IRUGO, show_const, NULL, n), \
+	  .priority = PRI_LOW, .value = v,}
+
+#define PLABEL(name, n, v) \
+	{.sda = SENSOR_ATTR(name, S_IRUGO, show_label, NULL, n), \
+	  .priority = PRI_LOW, .label = v,}
+
+static struct asc7621_param asc7621_params[] = {
+	PREAD(in0_input, 0, PRI_HIGH, 0x20, 0x13, 0, 0, in10),
+	PREAD(in1_input, 1, PRI_HIGH, 0x21, 0x18, 0, 0, in10),
+	PREAD(in2_input, 2, PRI_HIGH, 0x22, 0x11, 0, 0, in10),
+	PREAD(in3_input, 3, PRI_HIGH, 0x23, 0x12, 0, 0, in10),
+	PREAD(in4_input, 4, PRI_HIGH, 0x24, 0x14, 0, 0, in10),
+
+	PLABEL(in0_label, 0, "in0"),
+	PLABEL(in1_label, 1, "in1"),
+	PLABEL(in2_label, 2, "in2"),
+	PLABEL(in3_label, 3, "in3"),
+	PLABEL(in4_label, 4, "in4"),
+
+	PWRITE(in0_min, 0, PRI_LOW, 0x44, 0, 0, 0, in8),
+	PWRITE(in1_min, 1, PRI_LOW, 0x46, 0, 0, 0, in8),
+	PWRITE(in2_min, 2, PRI_LOW, 0x48, 0, 0, 0, in8),
+	PWRITE(in3_min, 3, PRI_LOW, 0x4a, 0, 0, 0, in8),
+	PWRITE(in4_min, 4, PRI_LOW, 0x4c, 0, 0, 0, in8),
+
+	PWRITE(in0_max, 0, PRI_LOW, 0x45, 0, 0, 0, in8),
+	PWRITE(in1_max, 1, PRI_LOW, 0x47, 0, 0, 0, in8),
+	PWRITE(in2_max, 2, PRI_LOW, 0x49, 0, 0, 0, in8),
+	PWRITE(in3_max, 3, PRI_LOW, 0x4b, 0, 0, 0, in8),
+	PWRITE(in4_max, 4, PRI_LOW, 0x4d, 0, 0, 0, in8),
+
+	PREAD(in0_alarm, 0, PRI_HIGH, 0x41, 0, 0x01, 0, bitmask),
+	PREAD(in1_alarm, 1, PRI_HIGH, 0x41, 0, 0x01, 1, bitmask),
+	PREAD(in2_alarm, 2, PRI_HIGH, 0x41, 0, 0x01, 2, bitmask),
+	PREAD(in3_alarm, 3, PRI_HIGH, 0x41, 0, 0x01, 3, bitmask),
+	PREAD(in4_alarm, 4, PRI_HIGH, 0x42, 0, 0x01, 0, bitmask),
+
+	PREAD(fan1_input, 0, PRI_HIGH, 0x29, 0x28, 0, 0, fan16),
+	PREAD(fan2_input, 1, PRI_HIGH, 0x2b, 0x2a, 0, 0, fan16),
+	PREAD(fan3_input, 2, PRI_HIGH, 0x2d, 0x2c, 0, 0, fan16),
+	PREAD(fan4_input, 3, PRI_HIGH, 0x2f, 0x2e, 0, 0, fan16),
+
+	PLABEL(fan1_label, 0, "fan1"),
+	PLABEL(fan2_label, 1, "fan2"),
+	PLABEL(fan3_label, 2, "fan3"),
+	PLABEL(fan4_label, 3, "fan4"),
+
+	PWRITE(fan1_min, 0, PRI_LOW, 0x55, 0x54, 0, 0, fan16),
+	PWRITE(fan2_min, 1, PRI_LOW, 0x57, 0x56, 0, 0, fan16),
+	PWRITE(fan3_min, 2, PRI_LOW, 0x59, 0x58, 0, 0, fan16),
+	PWRITE(fan4_min, 3, PRI_LOW, 0x5b, 0x5a, 0, 0, fan16),
+
+	PREAD(fan1_alarm, 0, PRI_HIGH, 0x42, 0, 0x01, 0, bitmask),
+	PREAD(fan2_alarm, 1, PRI_HIGH, 0x42, 0, 0x01, 1, bitmask),
+	PREAD(fan3_alarm, 2, PRI_HIGH, 0x42, 0, 0x01, 2, bitmask),
+	PREAD(fan4_alarm, 3, PRI_HIGH, 0x42, 0, 0x01, 3, bitmask),
+
+	PREAD(temp1_input, 0, PRI_HIGH, 0x25, 0x10, 0, 0, temp10),
+	PREAD(temp2_input, 1, PRI_HIGH, 0x26, 0x15, 0, 0, temp10),
+	PREAD(temp3_input, 2, PRI_HIGH, 0x27, 0x16, 0, 0, temp10),
+	PREAD(temp4_input, 3, PRI_HIGH, 0x33, 0x17, 0, 0, temp10),
+	PREAD(temp5_input, 4, PRI_HIGH, 0xf7, 0xf6, 0, 0, temp10),
+	PREAD(temp6_input, 5, PRI_HIGH, 0xf9, 0xf8, 0, 0, temp10),
+	PREAD(temp7_input, 6, PRI_HIGH, 0xfb, 0xfa, 0, 0, temp10),
+	PREAD(temp8_input, 7, PRI_HIGH, 0xfd, 0xfc, 0, 0, temp10),
+
+	PCONST(temp1_type, 0, 1),
+	PCONST(temp2_type, 1, 3),
+	PCONST(temp3_type, 2, 2),
+	PCONST(temp4_type, 3, 6),
+	PCONST(temp5_type, 4, 6),
+	PCONST(temp6_type, 5, 6),
+	PCONST(temp7_type, 6, 6),
+	PCONST(temp8_type, 7, 6),
+
+	PLABEL(temp1_label, 0, "cpu"),
+	PLABEL(temp2_label, 1, "internal"),
+	PLABEL(temp3_label, 2, "remote"),
+	PLABEL(temp4_label, 3, "peci 1"),
+	PLABEL(temp5_label, 4, "peci 1"),
+	PLABEL(temp6_label, 5, "peci 2"),
+	PLABEL(temp7_label, 6, "peci 3"),
+	PLABEL(temp8_label, 7, "peci 4"),
+
+	PWRITE(temp1_min, 0, PRI_LOW, 0x4e, 0, 0, 0, temp8),
+	PWRITE(temp2_min, 1, PRI_LOW, 0x50, 0, 0, 0, temp8),
+	PWRITE(temp3_min, 2, PRI_LOW, 0x52, 0, 0, 0, temp8),
+	PWRITE(temp4_min, 3, PRI_LOW, 0x34, 0, 0, 0, temp8),
+
+	PWRITE(temp1_max, 0, PRI_LOW, 0x4f, 0, 0, 0, temp8),
+	PWRITE(temp2_max, 1, PRI_LOW, 0x51, 0, 0, 0, temp8),
+	PWRITE(temp3_max, 2, PRI_LOW, 0x53, 0, 0, 0, temp8),
+	PWRITE(temp4_max, 3, PRI_LOW, 0x35, 0, 0, 0, temp8),
+
+	PREAD(temp1_alarm, 0, PRI_HIGH, 0x41, 0, 0x01, 4, bitmask),
+	PREAD(temp2_alarm, 1, PRI_HIGH, 0x41, 0, 0x01, 5, bitmask),
+	PREAD(temp3_alarm, 2, PRI_HIGH, 0x41, 0, 0x01, 6, bitmask),
+	PREAD(temp4_alarm, 3, PRI_HIGH, 0x43, 0, 0x01, 0, bitmask),
+
+	PWRITE(temp1_source, 0, PRI_LOW, 0x02, 0, 0x07, 4, bitmask),
+	PWRITE(temp2_source, 1, PRI_LOW, 0x02, 0, 0x07, 0, bitmask),
+	PWRITE(temp3_source, 2, PRI_LOW, 0x03, 0, 0x07, 4, bitmask),
+	PWRITE(temp4_source, 3, PRI_LOW, 0x03, 0, 0x07, 0, bitmask),
+	PCONST(temp5_source, 4, 4),
+	PCONST(temp6_source, 5, 5),
+	PCONST(temp7_source, 6, 6),
+	PCONST(temp8_source, 7, 7),
+
+	PWRITE(temp1_smoothing_enable, 0, PRI_LOW, 0x62, 0, 0x01, 3, bitmask),
+	PWRITE(temp2_smoothing_enable, 1, PRI_LOW, 0x63, 0, 0x01, 7, bitmask),
+	PWRITE(temp3_smoothing_enable, 2, PRI_LOW, 0x64, 0, 0x01, 3, bitmask),
+	PWRITE(temp4_smoothing_enable, 3, PRI_LOW, 0x3c, 0, 0x01, 3, bitmask),
+
+	PWRITE(temp1_smoothing_time, 0, PRI_LOW, 0x62, 0, 0x07, 0, temp_st),
+	PWRITE(temp2_smoothing_time, 1, PRI_LOW, 0x63, 0, 0x07, 4, temp_st),
+	PWRITE(temp3_smoothing_time, 2, PRI_LOW, 0x63, 0, 0x07, 0, temp_st),
+	PWRITE(temp4_smoothing_time, 3, PRI_LOW, 0x3c, 0, 0x07, 0, temp_st),
+
+	PWRITE(temp1_auto_point1_temp_hyst, 0, PRI_LOW, 0x6d, 0, 0x0f, 4,
+	       bitmask),
+	PWRITE(temp2_auto_point1_temp_hyst, 1, PRI_LOW, 0x6d, 0, 0x0f, 0,
+	       bitmask),
+	PWRITE(temp3_auto_point1_temp_hyst, 2, PRI_LOW, 0x6e, 0, 0x0f, 4,
+	       bitmask),
+	PWRITE(temp4_auto_point1_temp_hyst, 3, PRI_LOW, 0x6e, 0, 0x0f, 0,
+	       bitmask),
+
+	PREAD(temp1_auto_point2_temp_hyst, 0, PRI_LOW, 0x6d, 0, 0x0f, 4,
+	      bitmask),
+	PREAD(temp2_auto_point2_temp_hyst, 1, PRI_LOW, 0x6d, 0, 0x0f, 0,
+	      bitmask),
+	PREAD(temp3_auto_point2_temp_hyst, 2, PRI_LOW, 0x6e, 0, 0x0f, 4,
+	      bitmask),
+	PREAD(temp4_auto_point2_temp_hyst, 3, PRI_LOW, 0x6e, 0, 0x0f, 0,
+	      bitmask),
+
+	PWRITE(temp1_auto_point1_temp, 0, PRI_LOW, 0x67, 0, 0, 0, temp8),
+	PWRITE(temp2_auto_point1_temp, 1, PRI_LOW, 0x68, 0, 0, 0, temp8),
+	PWRITE(temp3_auto_point1_temp, 2, PRI_LOW, 0x69, 0, 0, 0, temp8),
+	PWRITE(temp4_auto_point1_temp, 3, PRI_LOW, 0x3b, 0, 0, 0, temp8),
+
+	PWRITEM(temp1_auto_point2_temp, 0, PRI_LOW, VAA(0x5f, 0x67), VAA(0),
+		VAA(0x0f), VAA(4), ap2_temp),
+	PWRITEM(temp2_auto_point2_temp, 1, PRI_LOW, VAA(0x60, 0x68), VAA(0),
+		VAA(0x0f), VAA(4), ap2_temp),
+	PWRITEM(temp3_auto_point2_temp, 2, PRI_LOW, VAA(0x61, 0x69), VAA(0),
+		VAA(0x0f), VAA(4), ap2_temp),
+	PWRITEM(temp4_auto_point2_temp, 3, PRI_LOW, VAA(0x3c, 0x3b), VAA(0),
+		VAA(0x0f), VAA(4), ap2_temp),
+
+	PWRITE(temp1_crit, 0, PRI_LOW, 0x6a, 0, 0, 0, temp8),
+	PWRITE(temp2_crit, 1, PRI_LOW, 0x6b, 0, 0, 0, temp8),
+	PWRITE(temp3_crit, 2, PRI_LOW, 0x6c, 0, 0, 0, temp8),
+	PWRITE(temp4_crit, 3, PRI_LOW, 0x3d, 0, 0, 0, temp8),
+
+	PWRITE(temp5_enable, 4, PRI_LOW, 0x0e, 0, 0x01, 0, bitmask),
+	PWRITE(temp6_enable, 5, PRI_LOW, 0x0e, 0, 0x01, 1, bitmask),
+	PWRITE(temp7_enable, 6, PRI_LOW, 0x0e, 0, 0x01, 2, bitmask),
+	PWRITE(temp8_enable, 7, PRI_LOW, 0x0e, 0, 0x01, 3, bitmask),
+
+	PWRITE(remote1_offset, 0, PRI_LOW, 0x1c, 0, 0, 0, temp62),
+	PWRITE(remote2_offset, 1, PRI_LOW, 0x1d, 0, 0, 0, temp62),
+
+	PWRITE(pwm1, 0, PRI_HIGH, 0x30, 0, 0, 0, u8),
+	PWRITE(pwm2, 1, PRI_HIGH, 0x31, 0, 0, 0, u8),
+	PWRITE(pwm3, 2, PRI_HIGH, 0x32, 0, 0, 0, u8),
+
+	PWRITE(pwm1_invert, 0, PRI_LOW, 0x5c, 0, 0x01, 4, bitmask),
+	PWRITE(pwm2_invert, 1, PRI_LOW, 0x5d, 0, 0x01, 4, bitmask),
+	PWRITE(pwm3_invert, 2, PRI_LOW, 0x5e, 0, 0x01, 4, bitmask),
+
+	PWRITEM(pwm1_enable, 0, PRI_LOW, VAA(0x5c, 0x5c, 0x62), VAA(0, 0, 0),
+		VAA(0x07, 0x01, 0x01), VAA(5, 3, 5), pwm_enable),
+	PWRITEM(pwm2_enable, 1, PRI_LOW, VAA(0x5d, 0x5d, 0x62), VAA(0, 0, 0),
+		VAA(0x07, 0x01, 0x01), VAA(5, 3, 6), pwm_enable),
+	PWRITEM(pwm3_enable, 2, PRI_LOW, VAA(0x5e, 0x5e, 0x62), VAA(0, 0, 0),
+		VAA(0x07, 0x01, 0x01), VAA(5, 3, 7), pwm_enable),
+
+	PWRITEM(pwm1_auto_channels, 0, PRI_LOW, VAA(0x5c, 0x5c), VAA(0, 0),
+		VAA(0x07, 0x01), VAA(5, 3), pwm_ac),
+	PWRITEM(pwm2_auto_channels, 1, PRI_LOW, VAA(0x5d, 0x5d), VAA(0, 0),
+		VAA(0x07, 0x01), VAA(5, 3), pwm_ac),
+	PWRITEM(pwm3_auto_channels, 2, PRI_LOW, VAA(0x5e, 0x5e), VAA(0, 0),
+		VAA(0x07, 0x01), VAA(5, 3), pwm_ac),
+
+	PWRITE(pwm1_auto_point1_pwm, 0, PRI_LOW, 0x64, 0, 0, 0, u8),
+	PWRITE(pwm2_auto_point1_pwm, 1, PRI_LOW, 0x65, 0, 0, 0, u8),
+	PWRITE(pwm3_auto_point1_pwm, 2, PRI_LOW, 0x66, 0, 0, 0, u8),
+
+	PWRITE(pwm1_auto_point2_pwm, 0, PRI_LOW, 0x38, 0, 0, 0, u8),
+	PWRITE(pwm2_auto_point2_pwm, 1, PRI_LOW, 0x39, 0, 0, 0, u8),
+	PWRITE(pwm3_auto_point2_pwm, 2, PRI_LOW, 0x3a, 0, 0, 0, u8),
+
+	PWRITE(pwm1_freq, 0, PRI_LOW, 0x5f, 0, 0x0f, 0, pwm_freq),
+	PWRITE(pwm2_freq, 1, PRI_LOW, 0x60, 0, 0x0f, 0, pwm_freq),
+	PWRITE(pwm3_freq, 2, PRI_LOW, 0x61, 0, 0x0f, 0, pwm_freq),
+
+	PREAD(pwm1_auto_zone_assigned, 0, PRI_LOW, 0, 0, 0x03, 2, bitmask),
+	PREAD(pwm2_auto_zone_assigned, 1, PRI_LOW, 0, 0, 0x03, 4, bitmask),
+	PREAD(pwm3_auto_zone_assigned, 2, PRI_LOW, 0, 0, 0x03, 6, bitmask),
+
+	PWRITE(pwm1_auto_spinup_time, 0, PRI_LOW, 0x5c, 0, 0x07, 0, pwm_ast),
+	PWRITE(pwm2_auto_spinup_time, 1, PRI_LOW, 0x5d, 0, 0x07, 0, pwm_ast),
+	PWRITE(pwm3_auto_spinup_time, 2, PRI_LOW, 0x5e, 0, 0x07, 0, pwm_ast),
+
+	PWRITE(peci_enable, 0, PRI_LOW, 0x40, 0, 0x01, 4, bitmask),
+	PWRITE(peci_avg, 0, PRI_LOW, 0x36, 0, 0x07, 0, bitmask),
+	PWRITE(peci_domain, 0, PRI_LOW, 0x36, 0, 0x01, 3, bitmask),
+	PWRITE(peci_legacy, 0, PRI_LOW, 0x36, 0, 0x01, 4, bitmask),
+	PWRITE(peci_diode, 0, PRI_LOW, 0x0e, 0, 0x07, 4, bitmask),
+	PWRITE(peci_4domain, 0, PRI_LOW, 0x0e, 0, 0x01, 4, bitmask),
+
+};
+
+static struct asc7621_data *asc7621_update_device(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct asc7621_data *data = i2c_get_clientdata(client);
+	int i;
+	int reg;
+
+/* 
+ * The asc7621 chips guarantee consistent reads of multi-byte values
+ * regardless of the order of the reads.  No special logic is needed
+ * so we can just read the registers in whatever  order they appear
+ * in the asc7621_params array.
+ */
+
+	mutex_lock(&data->update_lock);
+
+	/* Read all the high priority registers */
+
+	if (!data->valid ||
+	    time_after(jiffies, data->last_high_reading + INTERVAL_HIGH)) {
+
+		for (i = 0; i < last_high_priority; i++) {
+			reg = asc7621_register_high_priorities[i];
+			data->reg[reg] =
+			    i2c_smbus_read_byte_data(client, reg) & 0xff;
+		}
+		data->last_high_reading = jiffies;
+	}
+
+	/* last_reading */
+	/* Read all the low priority registers. */
+	if (!data->valid ||
+	    time_after(jiffies, data->last_high_reading + INTERVAL_LOW)) {
+
+		for (i = 0; i < last_low_priority; i++) {
+			reg = asc7621_register_low_priorities[i];
+			data->reg[reg] =
+			    i2c_smbus_read_byte_data(client, reg) & 0xff;
+		}
+		data->last_low_reading = jiffies;
+	}
+	/* last_reading */
+	data->valid = 1;
+
+	mutex_unlock(&data->update_lock);
+
+	return data;
+}
+
+/* Standard detection and initialization below */
+
+/* Helper function that checks if an address is valid
+ * for a particular chip.
+ */
+
+static inline int valid_address_for_chip(int chip_type, int address)
+{
+	int i = 0;
+	for (i = 0; asc7621_chips[chip_type].addresses[i] != I2C_CLIENT_END;
+	     i++) {
+		if (asc7621_chips[chip_type].addresses[i] == address) {
+			return (1);
+		}
+	}
+	return (0);
+}
+
+static void asc7621_init_client(struct i2c_client *client)
+{
+	u8 asc7621_register_priorities[255];
+	int value, i, j, msb, lsb;
+
+	dev_dbg(&client->dev, "Initializing device\n");
+
+	/* Warn if part was not "READY" */
+
+	value = i2c_smbus_read_byte_data(client, 0x40) & 0xff;
+
+	dev_dbg(&client->dev, "asc7621_REG_READY is: 0x%02x\n", value);
+	if (value & 0x02) {
+		dev_err(&client->dev,
+			"Client (%d,0x%02x) config is locked.\n",
+			i2c_adapter_id(client->adapter), client->addr);
+	};
+	if (!(value & 0x04)) {
+		dev_err(&client->dev, "Client (%d,0x%02x) is not ready.\n",
+			i2c_adapter_id(client->adapter), client->addr);
+	};
+
+	/* Start monitoring */
+
+	value = i2c_smbus_read_byte_data(client, 0x40) & 0xff;
+	/* Try to clear LOCK, Set START, save everything else */
+	value = (value & ~0x02) | 0x01;
+	dev_dbg(&client->dev, "Setting READY to: 0x%02x\n", value);
+	write_byte(0x40, value & 0xff);
+
+	dev_dbg(&client->dev, "Loading register arrays.\n");
+
+	/*
+	 * Collect all the registers needed into a single array.
+	 * This way, if a register isn't actually used for anything, 
+	 * we don't retrieve it.
+	 */
+
+	last_low_priority = 0;
+	last_high_priority = 0;
+	for (i = 0; i < ARRAY_SIZE(asc7621_params); i++) {
+		for (j = 0; j < 3; j++) {
+			msb = asc7621_params[i].msb[j];
+			if (asc7621_register_priorities[msb] != 1) {
+				asc7621_register_priorities[msb] = 1;
+				if (asc7621_params[i].priority == PRI_HIGH) {
+					asc7621_register_high_priorities
+					    [last_high_priority++] = msb;
+				} else {
+					asc7621_register_low_priorities
+					    [last_low_priority++] = msb;
+				}
+			}
+			lsb = asc7621_params[i].lsb[j];
+			if (asc7621_register_priorities[lsb] != 1) {
+				asc7621_register_priorities[lsb] = 1;
+				if (asc7621_params[i].priority == PRI_HIGH) {
+					asc7621_register_high_priorities
+					    [last_high_priority++] = lsb;
+				} else {
+					asc7621_register_low_priorities
+					    [last_low_priority++] = lsb;
+				}
+			}
+		}
+	}
+}
+
+static struct i2c_driver asc7621_driver;
+
+static int asc7621_detect(struct i2c_adapter *adapter, int address, int kind)
+{
+	int company, verstep;
+	struct i2c_client *client = NULL;
+	struct asc7621_data *data;
+	struct device *dev;
+	int err = 0;
+	int chip_index = 0;
+	int i = 0;
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
+		err = -EBUSY;
+		goto exit;
+	};
+
+	if (!(data = kzalloc(sizeof(struct asc7621_data), GFP_KERNEL))) {
+		err = -ENOMEM;
+		goto exit;
+	}
+
+	client = &data->client;
+	i2c_set_clientdata(client, data);
+	client->addr = address;
+	client->adapter = adapter;
+	client->driver = &asc7621_driver;
+	client->flags = 0;
+	dev = &client->dev;
+
+	if (kind > 0) {
+		if (kind < FIRST_CHIP || kind > LAST_CHIP) {
+			dev_err(&adapter->dev,
+				"Chip kind \"%d\" not handled by this driver.\n",
+				kind);
+			err = -EINVAL;
+			goto exit_free;
+		}
+		strlcpy(client->name, asc7621_chips[kind].name, I2C_NAME_SIZE);
+		dev_dbg(&adapter->dev, "Forced %s device at %d,0x%02x\n",
+			client->name, i2c_adapter_id(client->adapter),
+			client->addr);
+
+	} else {
+		dev_dbg(&adapter->dev,
+			"Autodetecting device at %d,0x%02x ...\n",
+			i2c_adapter_id(adapter), address);
+		kind = 0;
+		for (chip_index = FIRST_CHIP; chip_index <= LAST_CHIP;
+		     chip_index++) {
+
+			if (!valid_address_for_chip(chip_index, address)) {
+				continue;
+			}
+
+			company = i2c_smbus_read_byte_data(client,
+							   asc7621_chips
+							   [chip_index].
+							   company_reg)
+			    & 0xff;
+			verstep =
+			    i2c_smbus_read_byte_data(client,
+						     asc7621_chips
+						     [chip_index].verstep_reg) &
+			    0xff;
+
+			dev_dbg(&adapter->dev,
+				"Checking 0x%02x,0x%02x,0x%02x against 0x%02x,0x%02x\n",
+				address, company, verstep,
+				asc7621_chips[chip_index].company_id,
+				asc7621_chips[chip_index].verstep_id);
+
+			if (company ==
+			    asc7621_chips[chip_index].company_id &&
+			    verstep == asc7621_chips[chip_index].verstep_id) {
+				strlcpy(client->name,
+					asc7621_chips[chip_index].name,
+					I2C_NAME_SIZE);
+				kind = asc7621_chips[chip_index].chip_type;
+				dev_info(&adapter->dev, "  Matched %s\n",
+					 asc7621_chips[chip_index].name);
+				break;
+			}
+			dev_dbg(&adapter->dev, "  Didn't match %s\n",
+				asc7621_chips[chip_index].name);
+		}
+		if (kind <= 0) {
+			dev_err(&adapter->dev,
+				"asc7621 not found at %d,0x%02x.\n",
+				i2c_adapter_id(adapter), address);
+			err = -ENODEV;
+			goto exit_free;
+		}
+		dev_dbg(&adapter->dev,
+			"Detected %s device at %d,0x%02x with"
+			" COMPANY: 0x%02x and VERSTEP: 0x%02x\n",
+			client->name,
+			i2c_adapter_id(client->adapter),
+			client->addr, company, verstep);
+	}
+
+	data->valid = 0;
+	mutex_init(&data->update_lock);
+
+	/* Tell the I2C layer a new client has arrived */
+	if ((err = i2c_attach_client(client))) {
+		goto exit_free;
+	}
+
+	/* Initialize the asc7621 chip */
+	asc7621_init_client(client);
+
+	/* Create the sysfs entries */
+	for (i = 0; i < ARRAY_SIZE(asc7621_params); i++) {
+		if ((err =
+		     device_create_file(dev,
+					&(asc7621_params[i].sda.dev_attr)))) {
+			goto exit_remove;
+		}
+	}
+
+	data->class_dev = hwmon_device_register(&client->dev);
+	if (IS_ERR(data->class_dev)) {
+		err = PTR_ERR(data->class_dev);
+		goto exit_remove;
+	}
+
+	return (0);
+
+      exit_remove:
+	for (i = 0; i < ARRAY_SIZE(asc7621_params); i++) {
+		device_remove_file(dev, &(asc7621_params[i].sda.dev_attr));
+	}
+	i2c_detach_client(client);
+
+      exit_free:
+	kfree(data);
+      exit:
+	return err;
+
+}
+
+static int asc7621_detach_client(struct i2c_client *client)
+{
+	struct asc7621_data *data = i2c_get_clientdata(client);
+	int i;
+
+	hwmon_device_unregister(data->class_dev);
+
+	for (i = 0; i < ARRAY_SIZE(asc7621_params); i++) {
+		device_remove_file(&client->dev,
+				   &(asc7621_params[i].sda.dev_attr));
+	}
+
+	i2c_detach_client(client);
+	kfree(data);
+	return 0;
+}
+
+static int asc7621_attach_adapter(struct i2c_adapter *adapter)
+{
+	if (!(adapter->class & I2C_CLASS_HWMON))
+		return 0;
+
+	return i2c_probe(adapter, &addr_data, asc7621_detect);
+}
+
+static struct i2c_driver asc7621_driver = {
+	.driver = {
+		   .name = "asc7621",
+		   },
+	.attach_adapter = asc7621_attach_adapter,
+	.detach_client = asc7621_detach_client,
+};
+
+static int __init sm_asc7621_init(void)
+{
+	return i2c_add_driver(&asc7621_driver);
+}
+
+static void __exit sm_asc7621_exit(void)
+{
+	i2c_del_driver(&asc7621_driver);
+}
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("George Joseph");
+MODULE_DESCRIPTION("Andigilog aSC7621 and aSC7621a driver");
+
+module_init(sm_asc7621_init);
+module_exit(sm_asc7621_exit);
diff -urpN linux-2.6.25.10/drivers/hwmon/Kconfig linux-2.6.25.10-gtj/drivers/hwmon/Kconfig
--- linux-2.6.25.10/drivers/hwmon/Kconfig	2008-07-02 21:46:47.000000000 -0600
+++ linux-2.6.25.10-gtj/drivers/hwmon/Kconfig	2008-07-06 18:39:06.210473931 -0600
@@ -165,6 +165,19 @@ config SENSORS_K8TEMP
 	  This driver can also be built as a module.  If so, the module
 	  will be called k8temp.
 
+config SENSORS_ASC7621
+	tristate "Andigilog aSC7621"
+	depends on HWMON && I2C
+	help
+	  If you say yes here you get support for the aSC7621
+	  family of SMBus sensors chip found on most Intel X48, X38, 975, 
+	  965 and 945 desktop boards.  Currently supported chips:
+	  aSC7621
+	  aSC7621a
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called asc7621.
+	  
 config SENSORS_AMS
 	tristate "Apple Motion Sensor driver"
 	depends on PPC_PMAC && !PPC64 && INPUT && ((ADB_PMU && I2C = y) || (ADB_PMU && !I2C) || I2C) && EXPERIMENTAL
diff -urpN linux-2.6.25.10/drivers/hwmon/Makefile linux-2.6.25.10-gtj/drivers/hwmon/Makefile
--- linux-2.6.25.10/drivers/hwmon/Makefile	2008-07-02 21:46:47.000000000 -0600
+++ linux-2.6.25.10-gtj/drivers/hwmon/Makefile	2008-07-06 18:39:06.210473931 -0600
@@ -27,6 +27,7 @@ obj-$(CONFIG_SENSORS_ADT7470)	+= adt7470
 obj-$(CONFIG_SENSORS_ADT7473)	+= adt7473.o
 obj-$(CONFIG_SENSORS_APPLESMC)	+= applesmc.o
 obj-$(CONFIG_SENSORS_AMS)	+= ams/
+obj-$(CONFIG_SENSORS_ASC7621)	+= asc7621.o
 obj-$(CONFIG_SENSORS_ATXP1)	+= atxp1.o
 obj-$(CONFIG_SENSORS_CORETEMP)	+= coretemp.o
 obj-$(CONFIG_SENSORS_DME1737)	+= dme1737.o

[-- Attachment #3: Type: text/plain, Size: 153 bytes --]

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [lm-sensors] patch: asc7621 driver bug fixes
  2008-07-05 13:37 [lm-sensors] patch: asc7621 driver bug fixes Ken Milmore
                   ` (4 preceding siblings ...)
  2008-07-07  6:05 ` George Joseph
@ 2008-07-07 23:05 ` Ken Milmore
  2008-07-08  0:07 ` George Joseph
                   ` (4 subsequent siblings)
  10 siblings, 0 replies; 12+ messages in thread
From: Ken Milmore @ 2008-07-07 23:05 UTC (permalink / raw)
  To: lm-sensors

[-- Attachment #1: Type: text/plain, Size: 7402 bytes --]

George,

Thanks for incorporating those changes so quickly!

I'm afraid I didn't completely understand your new priority list code at 
first glance.  I will have to stare at it a bit longer until it sinks 
in! :-)

One thing that comes immediately to mind is that I think this 
table-stuffing should be done right up front, in a call from 
sm_asc7621_init(), not from asc7621_init_client().  Isn't 
asc7621_init_client called once for each aSC7621 *chip* that is 
detected?  If there are several chips (multi-CPU board?), then we don't 
want to repeat the table initialisation for each of them.

Another minor glitch is that I think you must be adding register address 
0 to the read lists, even though we don't need it (because 0 appears in 
all the empty entries of the msb and lsb arrays).  I'm sure this can 
easily be worked around, though.

Just to look at this from an alternative POV, I knocked up a read-only 
asc7621 mini-driver which doesn't use the table-driven paradigm (see 
patch).  It only supports the main inputs and alarms, but it could be 
very easily extended to cover about 90% of the functionality of the 
driver just by adding the required show_... functions, 
SENSOR_DEVICE_ATTR lines, and register reads.  For the remaining 10%, I 
realise that some of the PWM control knobs would be fiddly to implement, 
as they access bits of registers spattered all over the place.  I 
suppose that is what made you go down the table-driven design route in 
the first place.  Anyway, this is just FYI; I'm not trying to get you to 
alter course if you are happy with the existing design.

Cheers,

Ken.


George Joseph wrote:
> Here's the patch against 2.6.25.10.
> 
> signed-off-by: George Joseph <George.joseph@fairview5.com>
> 
> Ken, look at the new implementation of the priority registers.  It's still a loop initialization but it does get run only once at driver load.  With 98 registers of interest, I'm concerned that creating additional static arrays will make it hard to figure out what's going on.
> 
> george
> 
> 
>> -----Original Message-----
>> From: lm-sensors-bounces@lm-sensors.org [mailto:lm-sensors-bounces@lm-sensors.org] On Behalf Of George
>> Joseph
>> Sent: Sunday, July 06, 2008 4:18 PM
>> To: Ken Milmore; Hans de Goede
>> Cc: lm-sensors@lm-sensors.org
>> Subject: Re: [lm-sensors] patch: asc7621 driver bug fixes
>>
>> New patch will follow tomorrow ( I need to update my build environment).  Other comments in line...
>>
>>> -----Original Message-----
>>> From: Ken Milmore [mailto:ken@kenm.demon.co.uk]
>>> Sent: Sunday, July 06, 2008 10:49 AM
>>> To: Hans de Goede
>>> Cc: George Joseph; lm-sensors@lm-sensors.org
>>> Subject: Re: [lm-sensors] patch: asc7621 driver bug fixes
>>>
>>> Hans / George,
>>>
>>> Although things are shaping up nicely, I think there is still quite a bit left to do to completely
>>> review the driver.
>>>
>>> The aSC7621 appears to have a lot of bells and whistles for closed loop temperature control etc. and
>>> George has tried to support as much of this feature set as possible.  Unfortunately that means
>> there's
>>> quite a lot to review - I haven't really looked at PWM control yet.
>>>
>>> In any case, a few more things have come to light:
>>>
>>> 1.  In my last posting I wasn't completely sure if my "patch #2" was a good idea or not.  Having
>>> looked at the Andigilog specs, I have become convinced that it will be necessary after all.  The
>> main
>>> problem is with the two-byte registers.  To get the read-locking to work, the MSB and LSB must be
>> read
>>> together, one after the other (although it is unimportant which one is read first!).  So it is
>>> necessary to sequence the reads so that the two-byte registers are handled correctly.  My patch will
>>> correct this problem, although as I have said, I think that other, cleaner solutions may be
>> possible.
>>
>> Yep, my goof.  I thought I had this accounted for but it must have been in a earlier version of the
>> driver.  I solved it in the client_init code without creating a static array.  It only gets run once
>> at module load and it solves the msb-lsb issue.
>>
>>> 2.  The voltage scaling used in the driver appears to be slightly off from the nominal values given
>> in
>>> the spec.  To get the scaling exactly right, it is necessary to use a "muldiv" scheme which I've
>>> included in the patch (see below).
>> Yep, fixed.
>>
>>> 3.  I fixed a problem in store_fan16() whereby it wasn't possible to set the minimum fan RPM to
>> zero.
>>> The aSC7621 explicitly allows this, and of course it is useful to prevent spurious alarms for fans
>>> which are missing or stopped.
>> Yep, fixed.
>>
>>> The attached patch rolls up all my changes so far.  It applies against George's original patch of 29
>>> May.  It includes all the bug fixes from my last submission, the voltage scaling and fan fixes
>>> mentioned above and some tidying up of the high and low priority register lists.
>> All patches applied except for the register priorities and ordering which I fixed keeping the existing
>> paradigm.
>>
>>>
>>> To sum up, by now I'm very happy with the following functions of the driver:
>>>
>>> * Reading of voltages, fans and temperatures.
>>>
>>> * Setting of alarm limits and display of alarms.
>>>
>>> The following areas still need to be reviewed and perhaps improved:
>>>
>>> * PWM control.  A few scary lookup tables in the code that I don't understand yet!
>> Yeah, unfortunately, I can't think of a better way to translate what the chip exposes to the sysfs
>> specs.  Input definitely welcome.
>>
>>> * The miscellaneous configuration knobs (Input selection, PECI config etc).  I haven't tested these.
>> Actually, pay particular attention to PECI.  On my Conroes and Wolfdales, one of the peci temps
>> (temp5_input) appears to be the elusive and undocumented Tjmax. :)
>>
>>> * I think there is space for improvement in how the low-priority register list is handled.  A full
>>> minute between updates is probably too long.
>> Easy enough to change.  I was just copying from the other drivers.  Let me know what makes sense.
>>
>>> I hope this is of some use.  I will try to look at reviewing the remaining parts of the driver as
>> time
>>> allows.
>> Absolutely.  Keep sending feedback.
>>
>>> George, I'd appreciate your feedback on my patches so far; I realise you might want to approach some
>>> of these changes differently to the way I've done them.
>> Patches are great and I appreciate the time you're putting into the review!!
>>
>> george
>>
>>
>>> Best wishes,
>>>
>>> Ken.
>>>
>>>
>>> Hans de Goede wrote:
>>>> Ken Milmore wrote:
>>>>> George,
>>>>>
>>>>> Here are some suggested bug fixes for the asc7621 driver source which
>>>>> you posted to lm_sensors on 29 May.
>>>>> (http://lists.lm-sensors.org/pipermail/lm-sensors/2008-May/023257.htm
>>>>> l)
>>>>>
>>>> Thanks for doing this, I promised George to review this driver, but
>>>> sofar haven't gotten around to doing this. Any chance you could do a
>>>> complete review of it (or have you already done so, it sure looks that
>>>> way :)
>>>>
>>>> George, I assume you will post a new version of your patch soon with
>>>> these fixes incorperated?
>>>>
>>>> Regards,
>>>>
>>>> Hans
>>>>
>>
>> _______________________________________________
>> lm-sensors mailing list
>> lm-sensors@lm-sensors.org
>> http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

[-- Attachment #2: asc7621.c.diff --]
[-- Type: text/plain, Size: 12590 bytes --]

--- /dev/null	2005-11-21 03:22:37.000000000 +0000
+++ asc7621.c	2008-07-07 23:17:48.000000000 +0100
@@ -0,0 +1,382 @@
+/*
+    asc7621.c - Andigilog aSC7621 driver
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/jiffies.h>
+#include <linux/i2c.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+
+#define ASC7621_UPDATE_INTERVAL (HZ * 2)
+
+/* Addresses to scan */
+static unsigned short normal_i2c[] = {
+	0x2c, 0x2d, 0x2e, I2C_CLIENT_END
+};
+
+/* Insmod parameters */
+I2C_CLIENT_INSMOD_2(asc7621, asc7621a);
+
+/* Each client has this additional data */
+struct asc7621_data {
+	struct i2c_client client;
+	struct device *hwmon_dev;
+	struct mutex update_lock;
+	bool valid;
+	unsigned long last_updated; /* In jiffies */
+
+	/* Register values: */
+	u16 in_input[5];
+	u16 fan_input[4];
+	u16 temp_input[8];
+	u8 alarm[3];
+};
+
+static struct asc7621_data *asc7621_update_device(struct device *dev);
+
+/* Data Handlers: */
+
+static const s32 asc7621_in_scale[] = { 2500, 2250, 3300, 5000, 12000 };
+
+static ssize_t show_in_input(struct device *dev,
+		struct device_attribute *attr, char *buf) {
+
+	struct sensor_device_attribute *sda = to_sensor_dev_attr(attr);
+	struct asc7621_data *data = asc7621_update_device(dev);
+	int index = sda->index;
+	s32 val = data->in_input[index];
+
+	val >>= 6;
+	val *= asc7621_in_scale[index];
+	val /= (192 << 2);
+
+	return sprintf(buf, "%d\n", val);
+}
+
+static ssize_t show_fan_input(struct device *dev,
+		struct device_attribute *attr, char *buf) {
+
+	struct sensor_device_attribute *sda = to_sensor_dev_attr(attr);
+	struct asc7621_data *data = asc7621_update_device(dev);
+	int index = sda->index;
+	s32 val = data->fan_input[index];
+
+	if(val == 0)
+		val = -1;
+	else if(val == 0xffff)
+		val = 0;
+	else
+		val = (90000 * 60) / val;
+
+	return sprintf(buf, "%d\n", val);
+}
+
+static ssize_t show_temp_input(struct device *dev,
+		struct device_attribute *attr, char *buf) {
+
+	struct sensor_device_attribute *sda = to_sensor_dev_attr(attr);
+	struct asc7621_data *data = asc7621_update_device(dev);
+	int index = sda->index;
+	s32 val = (s16)(data->temp_input[index]); /* sign extension */
+
+	val >>= 6;
+	val *= 250;
+
+	return sprintf(buf, "%d\n", val);
+}
+
+static ssize_t show_alarm(struct device *dev,
+		struct device_attribute *attr, char *buf) {
+
+	struct sensor_device_attribute *sda = to_sensor_dev_attr(attr);
+	struct asc7621_data *data = asc7621_update_device(dev);
+	int index = sda->index;
+	s32 val = data->alarm[index / 8];
+
+	val >>= (index % 8);
+	val &= 0x1;
+
+	return sprintf(buf, "%d\n", val);
+}
+
+static u8 asc7621_read_byte(struct i2c_client *client, u8 adr)
+{
+	u8 val = i2c_smbus_read_byte_data(client, adr) & 0xff;
+	return val;
+}
+
+static u16 asc7621_read_word(struct i2c_client *client, u8 ladr, u8 madr)
+{
+	u8 lval = i2c_smbus_read_byte_data(client, ladr) & 0xff;
+	u8 mval = i2c_smbus_read_byte_data(client, madr) & 0xff;
+	return (mval << 8) + lval;
+}
+
+void asc7621_read(struct i2c_client *client, struct asc7621_data *data)
+{
+	data->in_input[0]   = asc7621_read_word(client, 0x13, 0x20);
+	data->in_input[1]   = asc7621_read_word(client, 0x18, 0x21);
+	data->in_input[2]   = asc7621_read_word(client, 0x11, 0x22);
+	data->in_input[3]   = asc7621_read_word(client, 0x12, 0x23);
+	data->in_input[4]   = asc7621_read_word(client, 0x14, 0x24);
+
+	data->fan_input[0]  = asc7621_read_word(client, 0x28, 0x29);
+	data->fan_input[1]  = asc7621_read_word(client, 0x2a, 0x2b);
+	data->fan_input[2]  = asc7621_read_word(client, 0x2c, 0x2d);
+	data->fan_input[3]  = asc7621_read_word(client, 0x2e, 0x2f);
+
+	data->temp_input[0] = asc7621_read_word(client, 0x10, 0x25);
+	data->temp_input[1] = asc7621_read_word(client, 0x15, 0x26);
+	data->temp_input[2] = asc7621_read_word(client, 0x16, 0x27);
+	data->temp_input[3] = asc7621_read_word(client, 0x17, 0x33);
+	data->temp_input[4] = asc7621_read_word(client, 0xf6, 0xf7);
+	data->temp_input[5] = asc7621_read_word(client, 0xf8, 0xf9);
+	data->temp_input[6] = asc7621_read_word(client, 0xfa, 0xfb);
+	data->temp_input[7] = asc7621_read_word(client, 0xfc, 0xfd);
+
+	data->alarm[0]      = asc7621_read_byte(client, 0x41);
+	data->alarm[0]      = asc7621_read_byte(client, 0x42);
+	data->alarm[0]      = asc7621_read_byte(client, 0x43);
+}
+
+static struct asc7621_data *asc7621_update_device(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct asc7621_data *data = i2c_get_clientdata(client);
+
+	mutex_lock(&data->update_lock);
+
+	if (!data->valid ||
+		time_after(jiffies, data->last_updated + ASC7621_UPDATE_INTERVAL)) {
+
+		asc7621_read(client, data);
+
+		data->last_updated = jiffies;
+		data->valid = true;
+	}
+
+	mutex_unlock(&data->update_lock);
+
+	return data;
+}
+
+/* Sysfs attributes: */
+
+static SENSOR_DEVICE_ATTR(in0_input,   S_IRUGO, show_in_input,   NULL, 0);
+static SENSOR_DEVICE_ATTR(in1_input,   S_IRUGO, show_in_input,   NULL, 1);
+static SENSOR_DEVICE_ATTR(in2_input,   S_IRUGO, show_in_input,   NULL, 2);
+static SENSOR_DEVICE_ATTR(in3_input,   S_IRUGO, show_in_input,   NULL, 3);
+static SENSOR_DEVICE_ATTR(in4_input,   S_IRUGO, show_in_input,   NULL, 4);
+
+static SENSOR_DEVICE_ATTR(fan1_input,  S_IRUGO, show_fan_input,  NULL, 0);
+static SENSOR_DEVICE_ATTR(fan2_input,  S_IRUGO, show_fan_input,  NULL, 1);
+static SENSOR_DEVICE_ATTR(fan3_input,  S_IRUGO, show_fan_input,  NULL, 2);
+static SENSOR_DEVICE_ATTR(fan4_input,  S_IRUGO, show_fan_input,  NULL, 3);
+
+static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp_input, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp_input, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp_input, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_temp_input, NULL, 3);
+static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, show_temp_input, NULL, 4);
+static SENSOR_DEVICE_ATTR(temp6_input, S_IRUGO, show_temp_input, NULL, 5);
+static SENSOR_DEVICE_ATTR(temp7_input, S_IRUGO, show_temp_input, NULL, 6);
+static SENSOR_DEVICE_ATTR(temp8_input, S_IRUGO, show_temp_input, NULL, 7);
+
+static SENSOR_DEVICE_ATTR(in0_alarm,   S_IRUGO, show_alarm,      NULL, 0);
+static SENSOR_DEVICE_ATTR(in1_alarm,   S_IRUGO, show_alarm,      NULL, 1);
+static SENSOR_DEVICE_ATTR(in2_alarm,   S_IRUGO, show_alarm,      NULL, 2);
+static SENSOR_DEVICE_ATTR(in3_alarm,   S_IRUGO, show_alarm,      NULL, 3);
+static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm,      NULL, 4);
+static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm,      NULL, 5);
+static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_alarm,      NULL, 6);
+static SENSOR_DEVICE_ATTR(in4_alarm,   S_IRUGO, show_alarm,      NULL, 8);
+static SENSOR_DEVICE_ATTR(fan1_alarm,  S_IRUGO, show_alarm,      NULL, 10);
+static SENSOR_DEVICE_ATTR(fan2_alarm,  S_IRUGO, show_alarm,      NULL, 11);
+static SENSOR_DEVICE_ATTR(fan3_alarm,  S_IRUGO, show_alarm,      NULL, 12);
+static SENSOR_DEVICE_ATTR(fan4_alarm,  S_IRUGO, show_alarm,      NULL, 13);
+static SENSOR_DEVICE_ATTR(temp4_alarm, S_IRUGO, show_alarm,      NULL, 16);
+
+static struct attribute *asc7621_attributes[] = {
+	&sensor_dev_attr_in0_input.dev_attr.attr,
+	&sensor_dev_attr_in1_input.dev_attr.attr,
+	&sensor_dev_attr_in2_input.dev_attr.attr,
+	&sensor_dev_attr_in3_input.dev_attr.attr,
+	&sensor_dev_attr_in4_input.dev_attr.attr,
+	&sensor_dev_attr_fan1_input.dev_attr.attr,
+	&sensor_dev_attr_fan2_input.dev_attr.attr,
+	&sensor_dev_attr_fan3_input.dev_attr.attr,
+	&sensor_dev_attr_fan4_input.dev_attr.attr,
+	&sensor_dev_attr_temp1_input.dev_attr.attr,
+	&sensor_dev_attr_temp2_input.dev_attr.attr,
+	&sensor_dev_attr_temp3_input.dev_attr.attr,
+	&sensor_dev_attr_temp4_input.dev_attr.attr,
+	&sensor_dev_attr_temp5_input.dev_attr.attr,
+	&sensor_dev_attr_temp6_input.dev_attr.attr,
+	&sensor_dev_attr_temp7_input.dev_attr.attr,
+	&sensor_dev_attr_temp8_input.dev_attr.attr,
+	&sensor_dev_attr_in0_alarm.dev_attr.attr,
+	&sensor_dev_attr_in1_alarm.dev_attr.attr,
+	&sensor_dev_attr_in2_alarm.dev_attr.attr,
+	&sensor_dev_attr_in3_alarm.dev_attr.attr,
+	&sensor_dev_attr_in4_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp1_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp2_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp3_alarm.dev_attr.attr,
+	&sensor_dev_attr_fan1_alarm.dev_attr.attr,
+	&sensor_dev_attr_fan2_alarm.dev_attr.attr,
+	&sensor_dev_attr_fan3_alarm.dev_attr.attr,
+	&sensor_dev_attr_fan4_alarm.dev_attr.attr,
+	&sensor_dev_attr_temp4_alarm.dev_attr.attr,
+	NULL
+};
+
+static const struct attribute_group asc7621_group = {
+	.attrs = asc7621_attributes,
+};
+
+static int asc7621_attach_adapter(struct i2c_adapter *adapter);
+static int asc7621_detach_client(struct i2c_client *client);
+
+static struct i2c_driver asc7621_driver = {
+	.driver = {
+		.name = "asc7621",
+	},
+	.attach_adapter = asc7621_attach_adapter,
+	.detach_client = asc7621_detach_client,
+};
+
+static int asc7621_detect(struct i2c_adapter *adapter, int address, int kind)
+{
+	struct i2c_client *client;
+	struct asc7621_data *data;
+	const char *name;
+	int err = 0;
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+		goto exit;
+
+	/* Create the client structure */
+	if (!(data = kzalloc(sizeof(struct asc7621_data), GFP_KERNEL))) {
+		err = -ENOMEM;
+		goto exit;
+	}
+
+	/* Fill in enough client fields so that we can read from the chip */
+	client = &data->client;
+	i2c_set_clientdata(client, data);
+	client->addr = address;
+	client->adapter = adapter;
+	client->driver = &asc7621_driver;
+	client->flags = 0;
+
+	/* A negative kind means that the driver was loaded with no force
+	   parameter (default), so we must identify the chip. */
+	if (kind < 0) {
+		u8 company, verstep;
+
+		company = i2c_smbus_read_byte_data(client, 0x3e); /* Company ID */
+		if(company != 0x61) /* Andigilog */
+			goto exit_free;
+
+		verstep = i2c_smbus_read_byte_data(client, 0x3f); /* Version/stepping */
+		if(verstep == 0x6c) /* aSC7621 */
+			kind = asc7621;
+		else if(verstep == 0x6d) /* aSC7621a */
+			kind = asc7621a;
+		else
+			goto exit_free;
+	}
+
+	/* Give it the proper name */
+	if (kind == asc7621)
+		name = "asc7621";
+	else if (kind == asc7621a)
+		name = "asc7621a";
+	else
+	{
+		err = -ENODEV;
+		goto exit_free;
+	}
+
+	/* Fill in the remaining client fields */
+	strlcpy(client->name, name, I2C_NAME_SIZE);
+	data->valid = false;
+	mutex_init(&data->update_lock);
+
+	/* Tell the I2C layer a new client has arrived */
+	if ((err = i2c_attach_client(client)))
+		goto exit_free;
+
+	/* Register sysfs hooks */
+	if ((err = sysfs_create_group(&client->dev.kobj, &asc7621_group)))
+		goto exit_detach;
+
+	data->hwmon_dev = hwmon_device_register(&client->dev);
+	if (IS_ERR(data->hwmon_dev)) {
+		err = PTR_ERR(data->hwmon_dev);
+		goto exit_remove;
+	}
+
+	return 0;
+
+exit_remove:
+	sysfs_remove_group(&client->dev.kobj, &asc7621_group);
+exit_detach:
+	i2c_detach_client(client);
+exit_free:
+	kfree(data);
+exit:
+	return err;
+}
+
+static int asc7621_detach_client(struct i2c_client *client)
+{
+	struct asc7621_data *data = i2c_get_clientdata(client);
+	hwmon_device_unregister(data->hwmon_dev);
+	sysfs_remove_group(&client->dev.kobj, &asc7621_group);
+	i2c_detach_client(client);
+	kfree(data);
+	return 0;
+}
+
+static int asc7621_attach_adapter(struct i2c_adapter *adapter)
+{
+	if (!(adapter->class & I2C_CLASS_HWMON))
+			return 0;
+	return i2c_probe(adapter, &addr_data, asc7621_detect);
+}
+
+static int __init sm_asc7621_init(void)
+{
+	return i2c_add_driver(&asc7621_driver);
+}
+
+static void __exit sm_asc7621_exit(void)
+{
+	i2c_del_driver(&asc7621_driver);
+}
+
+MODULE_DESCRIPTION("Andigilog aSC7621 driver");
+MODULE_LICENSE("GPL");
+
+module_init(sm_asc7621_init);
+module_exit(sm_asc7621_exit);

[-- Attachment #3: Type: text/plain, Size: 153 bytes --]

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [lm-sensors] patch: asc7621 driver bug fixes
  2008-07-05 13:37 [lm-sensors] patch: asc7621 driver bug fixes Ken Milmore
                   ` (5 preceding siblings ...)
  2008-07-07 23:05 ` Ken Milmore
@ 2008-07-08  0:07 ` George Joseph
  2008-07-08  5:19 ` Hans de Goede
                   ` (3 subsequent siblings)
  10 siblings, 0 replies; 12+ messages in thread
From: George Joseph @ 2008-07-08  0:07 UTC (permalink / raw)
  To: lm-sensors

On Mon, 2008-07-07 at 17:05 -0600, Ken Milmore wrote:
> George,
> 
> Thanks for incorporating those changes so quickly!
> 
> I'm afraid I didn't completely understand your new priority list code at
> first glance.  I will have to stare at it a bit longer until it sinks
> in! :-)
> 
> One thing that comes immediately to mind is that I think this
> table-stuffing should be done right up front, in a call from
> sm_asc7621_init(), not from asc7621_init_client().  Isn't
> asc7621_init_client called once for each aSC7621 *chip* that is
> detected?  If there are several chips (multi-CPU board?), then we don't
> want to repeat the table initialisation for each of them.

Ah.  I hand't even considered multiple chips.  I haven't heard of such
an implementation but you're correct in that I should handle it.  I'll
move it to the module init code.

> 
> Another minor glitch is that I think you must be adding register address
> 0 to the read lists, even though we don't need it (because 0 appears in
> all the empty entries of the msb and lsb arrays).  I'm sure this can
> easily be worked around, though.

We still need to read it at least once because it has the pwm auto zone
assignments I let the first default 0 in the msb or lsb arrays take care
of it.  If you dump the array after it's loaded you'll see 0x20, 0x13
(in0) then 0x00 (because it's the default of the unused msb/lsb) then
0x21, 0x18 (in1), etc.  That's the only place 0x00 appears.

Am I missing something?

> 
> Just to look at this from an alternative POV, I knocked up a read-only
> asc7621 mini-driver which doesn't use the table-driven paradigm (see
> patch).  It only supports the main inputs and alarms, but it could be
> very easily extended to cover about 90% of the functionality of the
> driver just by adding the required show_... functions,
> SENSOR_DEVICE_ATTR lines, and register reads.  For the remaining 10%, I
> realise that some of the PWM control knobs would be fiddly to implement,
> as they access bits of registers spattered all over the place.  I
> suppose that is what made you go down the table-driven design route in
> the first place.  Anyway, this is just FYI; I'm not trying to get you to
> alter course if you are happy with the existing design.

It's always that last 10% that gets you. :)

When I started the driver last year, I cloned one of the LM* drivers and
it worked just fine for the basic stuff just by modifying the chip and
manfacturer id.  Then I thought if I'm going to do a driver for this
thing, I probably should expose as much of the chip's functionality as I
could.   With 98 registers to read and 160+ sysfs entries to create
though, I quickly realized it would be impossible to code and maintain
the driver using the existing paradigms.  Hence the table driven
approach.  The details of the registers and sysfs entries are all in one
easy-to-read table.

I think at this point the approach will have to stand unless it's going
to prevent acceptance of the driver.  If it is going to be an issue I
think I'm just going to have to give up on it for a while.  I only have
limited windows of time to work on it.

Please keep the comments and critiques coming though!!

george

> Cheers,
> 
> Ken.
> 
> 
> George Joseph wrote:
> > Here's the patch against 2.6.25.10.
> >
> > signed-off-by: George Joseph <George.joseph@fairview5.com>
> >
> > Ken, look at the new implementation of the priority registers.  It's still a loop initialization but it does get run only once at driver load.  With 98 registers of interest, I'm concerned that creating additional static arrays will make it hard to figure out what's going on.
> >
> > george
> >
> >
> >> -----Original Message-----
> >> From: lm-sensors-bounces@lm-sensors.org [mailto:lm-sensors-bounces@lm-sensors.org] On Behalf Of George
> >> Joseph
> >> Sent: Sunday, July 06, 2008 4:18 PM
> >> To: Ken Milmore; Hans de Goede
> >> Cc: lm-sensors@lm-sensors.org
> >> Subject: Re: [lm-sensors] patch: asc7621 driver bug fixes
> >>
> >> New patch will follow tomorrow ( I need to update my build environment).  Other comments in line...
> >>
> >>> -----Original Message-----
> >>> From: Ken Milmore [mailto:ken@kenm.demon.co.uk]
> >>> Sent: Sunday, July 06, 2008 10:49 AM
> >>> To: Hans de Goede
> >>> Cc: George Joseph; lm-sensors@lm-sensors.org
> >>> Subject: Re: [lm-sensors] patch: asc7621 driver bug fixes
> >>>
> >>> Hans / George,
> >>>
> >>> Although things are shaping up nicely, I think there is still quite a bit left to do to completely
> >>> review the driver.
> >>>
> >>> The aSC7621 appears to have a lot of bells and whistles for closed loop temperature control etc. and
> >>> George has tried to support as much of this feature set as possible.  Unfortunately that means
> >> there's
> >>> quite a lot to review - I haven't really looked at PWM control yet.
> >>>
> >>> In any case, a few more things have come to light:
> >>>
> >>> 1.  In my last posting I wasn't completely sure if my "patch #2" was a good idea or not.  Having
> >>> looked at the Andigilog specs, I have become convinced that it will be necessary after all.  The
> >> main
> >>> problem is with the two-byte registers.  To get the read-locking to work, the MSB and LSB must be
> >> read
> >>> together, one after the other (although it is unimportant which one is read first!).  So it is
> >>> necessary to sequence the reads so that the two-byte registers are handled correctly.  My patch will
> >>> correct this problem, although as I have said, I think that other, cleaner solutions may be
> >> possible.
> >>
> >> Yep, my goof.  I thought I had this accounted for but it must have been in a earlier version of the
> >> driver.  I solved it in the client_init code without creating a static array.  It only gets run once
> >> at module load and it solves the msb-lsb issue.
> >>
> >>> 2.  The voltage scaling used in the driver appears to be slightly off from the nominal values given
> >> in
> >>> the spec.  To get the scaling exactly right, it is necessary to use a "muldiv" scheme which I've
> >>> included in the patch (see below).
> >> Yep, fixed.
> >>
> >>> 3.  I fixed a problem in store_fan16() whereby it wasn't possible to set the minimum fan RPM to
> >> zero.
> >>> The aSC7621 explicitly allows this, and of course it is useful to prevent spurious alarms for fans
> >>> which are missing or stopped.
> >> Yep, fixed.
> >>
> >>> The attached patch rolls up all my changes so far.  It applies against George's original patch of 29
> >>> May.  It includes all the bug fixes from my last submission, the voltage scaling and fan fixes
> >>> mentioned above and some tidying up of the high and low priority register lists.
> >> All patches applied except for the register priorities and ordering which I fixed keeping the existing
> >> paradigm.
> >>
> >>>
> >>> To sum up, by now I'm very happy with the following functions of the driver:
> >>>
> >>> * Reading of voltages, fans and temperatures.
> >>>
> >>> * Setting of alarm limits and display of alarms.
> >>>
> >>> The following areas still need to be reviewed and perhaps improved:
> >>>
> >>> * PWM control.  A few scary lookup tables in the code that I don't understand yet!
> >> Yeah, unfortunately, I can't think of a better way to translate what the chip exposes to the sysfs
> >> specs.  Input definitely welcome.
> >>
> >>> * The miscellaneous configuration knobs (Input selection, PECI config etc).  I haven't tested these.
> >> Actually, pay particular attention to PECI.  On my Conroes and Wolfdales, one of the peci temps
> >> (temp5_input) appears to be the elusive and undocumented Tjmax. :)
> >>
> >>> * I think there is space for improvement in how the low-priority register list is handled.  A full
> >>> minute between updates is probably too long.
> >> Easy enough to change.  I was just copying from the other drivers.  Let me know what makes sense.
> >>
> >>> I hope this is of some use.  I will try to look at reviewing the remaining parts of the driver as
> >> time
> >>> allows.
> >> Absolutely.  Keep sending feedback.
> >>
> >>> George, I'd appreciate your feedback on my patches so far; I realise you might want to approach some
> >>> of these changes differently to the way I've done them.
> >> Patches are great and I appreciate the time you're putting into the review!!
> >>
> >> george
> >>
> >>
> >>> Best wishes,
> >>>
> >>> Ken.
> >>>
> >>>
> >>> Hans de Goede wrote:
> >>>> Ken Milmore wrote:
> >>>>> George,
> >>>>>
> >>>>> Here are some suggested bug fixes for the asc7621 driver source which
> >>>>> you posted to lm_sensors on 29 May.
> >>>>> (http://lists.lm-sensors.org/pipermail/lm-sensors/2008-May/023257.htm
> >>>>> l)
> >>>>>
> >>>> Thanks for doing this, I promised George to review this driver, but
> >>>> sofar haven't gotten around to doing this. Any chance you could do a
> >>>> complete review of it (or have you already done so, it sure looks that
> >>>> way :)
> >>>>
> >>>> George, I assume you will post a new version of your patch soon with
> >>>> these fixes incorperated?
> >>>>
> >>>> Regards,
> >>>>
> >>>> Hans
> >>>>
> >>
> >> _______________________________________________
> >> lm-sensors mailing list
> >> lm-sensors@lm-sensors.org
> >> http://lists.lm-sensors.org/mailman/listinfo/lm-sensors


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [lm-sensors] patch: asc7621 driver bug fixes
  2008-07-05 13:37 [lm-sensors] patch: asc7621 driver bug fixes Ken Milmore
                   ` (6 preceding siblings ...)
  2008-07-08  0:07 ` George Joseph
@ 2008-07-08  5:19 ` Hans de Goede
  2008-07-08  7:31 ` George Joseph
                   ` (2 subsequent siblings)
  10 siblings, 0 replies; 12+ messages in thread
From: Hans de Goede @ 2008-07-08  5:19 UTC (permalink / raw)
  To: lm-sensors

George Joseph wrote:
> It's always that last 10% that gets you. :)
> 
> When I started the driver last year, I cloned one of the LM* drivers and
> it worked just fine for the basic stuff just by modifying the chip and
> manfacturer id.  Then I thought if I'm going to do a driver for this
> thing, I probably should expose as much of the chip's functionality as I
> could.   With 98 registers to read and 160+ sysfs entries to create
> though, I quickly realized it would be impossible to code and maintain
> the driver using the existing paradigms.  Hence the table driven
> approach.  The details of the registers and sysfs entries are all in one
> easy-to-read table.
> 
> I think at this point the approach will have to stand unless it's going
> to prevent acceptance of the driver.  If it is going to be an issue I
> think I'm just going to have to give up on it for a while.  I only have
> limited windows of time to work on it.
> 

The table driven approach should not be a problem. Atleast one other driver 
(abituguru3) uses lots of tables too. Disclaimer: I'ven't look (much) at the 
code yet.

Regards,

Hans

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [lm-sensors] patch: asc7621 driver bug fixes
  2008-07-05 13:37 [lm-sensors] patch: asc7621 driver bug fixes Ken Milmore
                   ` (7 preceding siblings ...)
  2008-07-08  5:19 ` Hans de Goede
@ 2008-07-08  7:31 ` George Joseph
  2008-07-08 23:24 ` Ken Milmore
  2008-07-09  5:23 ` George Joseph
  10 siblings, 0 replies; 12+ messages in thread
From: George Joseph @ 2008-07-08  7:31 UTC (permalink / raw)
  To: lm-sensors



> -----Original Message-----
> From: Hans de Goede [mailto:j.w.r.degoede@hhs.nl]
> Sent: Monday, July 07, 2008 11:20 PM
> To: George Joseph
> Cc: Ken Milmore; lm-sensors@lm-sensors.org
> Subject: Re: [lm-sensors] patch: asc7621 driver bug fixes
>
> George Joseph wrote:
> > It's always that last 10% that gets you. :)
> >
> > When I started the driver last year, I cloned one of the LM* drivers and
> > it worked just fine for the basic stuff just by modifying the chip and
> > manfacturer id.  Then I thought if I'm going to do a driver for this
> > thing, I probably should expose as much of the chip's functionality as I
> > could.   With 98 registers to read and 160+ sysfs entries to create
> > though, I quickly realized it would be impossible to code and maintain
> > the driver using the existing paradigms.  Hence the table driven
> > approach.  The details of the registers and sysfs entries are all in one
> > easy-to-read table.
> >
> > I think at this point the approach will have to stand unless it's going
> > to prevent acceptance of the driver.  If it is going to be an issue I
> > think I'm just going to have to give up on it for a while.  I only have
> > limited windows of time to work on it.
> >
>
> The table driven approach should not be a problem. Atleast one other driver
> (abituguru3) uses lots of tables too. Disclaimer: I'ven't look (much) at the
> code yet.
>
> Regards,
>
> Hans

Good to know Hans.  Thanks.


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [lm-sensors] patch: asc7621 driver bug fixes
  2008-07-05 13:37 [lm-sensors] patch: asc7621 driver bug fixes Ken Milmore
                   ` (8 preceding siblings ...)
  2008-07-08  7:31 ` George Joseph
@ 2008-07-08 23:24 ` Ken Milmore
  2008-07-09  5:23 ` George Joseph
  10 siblings, 0 replies; 12+ messages in thread
From: Ken Milmore @ 2008-07-08 23:24 UTC (permalink / raw)
  To: lm-sensors


George Joseph wrote:
> We still need to read it at least once because it has the pwm auto zone
> assignments I let the first default 0 in the msb or lsb arrays take care
> of it.  If you dump the array after it's loaded you'll see 0x20, 0x13
> (in0) then 0x00 (because it's the default of the unused msb/lsb) then
> 0x21, 0x18 (in1), etc.  That's the only place 0x00 appears.
> 
> Am I missing something?

No, you're not missing anything. :-)  I understand now, but see below.

> When I started the driver last year, I cloned one of the LM* drivers and
> it worked just fine for the basic stuff just by modifying the chip and
> manfacturer id.  Then I thought if I'm going to do a driver for this
> thing, I probably should expose as much of the chip's functionality as I
> could.   With 98 registers to read and 160+ sysfs entries to create
> though, I quickly realized it would be impossible to code and maintain
> the driver using the existing paradigms.  Hence the table driven
> approach.  The details of the registers and sysfs entries are all in one
> easy-to-read table.

It isn't your big table of constants in the code (asc7621_params) that I 
have the problem with, it is the need to reverse-index this table at 
runtime which I am calling into question.

I can see why you want to do it this way.  You have a many-to-many 
mapping between device registers and sysfs entries, which is expressed 
in the main table.  You could just walk down that table and read the 
device registers as required, but this would mean reading some registers 
more than once.  Indeed, some of the flags registers would be read many 
times.  And each read over I2C is *very* time-costly.  So you need 
separate lists of all the registers to be read, with duplicates removed 
so that each is only read once.  No controversy there.

What I'm having trouble with is why these lists (of low and high 
priority registers) need to be generated at runtime.  It would certainly 
be nice and elegant if this information could be obtained directly from 
the main asc7621_params table, without iterative re-indexing, but I 
can't think of a way to do it other than by re-designing the driver. 
That's why I suggested simply hard-coding the lists.

I realise you may think that this would wreck the maintainability of 
your main table, but its not as bad as it sounds!  After all, from the 
table I failed to notice where register zero appears in the read lists; 
it is added as a kind of "side effect".  That ambiguity wouldn't have 
arisen if the lists had been in front of me in the source!  And you get 
to save on half a kilobyte of unnecessary array space into the bargain...

Anyway, if I have failed to dissuade you, and you still really, really 
want to persist with the dynamic list creation, then you might want to 
look at the following further points:

1.  Declaring a u8[256] as an automatic array on the rather small kernel 
stack is probably a bad idea.

2.  C doesn't initialise automatic data to zero so you will have to 
memset the asc7621_register_priorities array before use.  (I suspect you 
have been lucky here with the specificity of your "!= 1" check on the 
array contents!)

3.  You could save a bit of space by putting both the high and low 
priority lists into the same array:  The size of both lists together 
cannot exceed 256.  This doesn't really matter, though.

> I think at this point the approach will have to stand unless it's going
> to prevent acceptance of the driver.  If it is going to be an issue I
> think I'm just going to have to give up on it for a while.  I only have
> limited windows of time to work on it.

Fair enough.  I don't have any say in the matter, nor would I want to. 
I'm only expressing opinions, which you can take or leave.  Open debate 
applied to open source development.  Thanks for listening! ;-)

Kind regards,

Ken.


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [lm-sensors] patch: asc7621 driver bug fixes
  2008-07-05 13:37 [lm-sensors] patch: asc7621 driver bug fixes Ken Milmore
                   ` (9 preceding siblings ...)
  2008-07-08 23:24 ` Ken Milmore
@ 2008-07-09  5:23 ` George Joseph
  10 siblings, 0 replies; 12+ messages in thread
From: George Joseph @ 2008-07-09  5:23 UTC (permalink / raw)
  To: lm-sensors

[-- Attachment #1: Type: text/plain, Size: 4578 bytes --]

Latest version attached...

Signed-off-by: George Joseph <George.joseph@fairview5.com>

Comments below...

> -----Original Message-----
> From: Ken Milmore [mailto:ken@kenm.demon.co.uk]
> Sent: Tuesday, July 08, 2008 5:24 PM
> To: George Joseph
> Cc: Hans de Goede; lm-sensors@lm-sensors.org
> Subject: Re: [lm-sensors] patch: asc7621 driver bug fixes
>
>
> George Joseph wrote:
> > We still need to read it at least once because it has the pwm auto zone
> > assignments I let the first default 0 in the msb or lsb arrays take care
> > of it.  If you dump the array after it's loaded you'll see 0x20, 0x13
> > (in0) then 0x00 (because it's the default of the unused msb/lsb) then
> > 0x21, 0x18 (in1), etc.  That's the only place 0x00 appears.
> >
> > Am I missing something?
>
> No, you're not missing anything. :-)  I understand now, but see below.
>
> > When I started the driver last year, I cloned one of the LM* drivers and
> > it worked just fine for the basic stuff just by modifying the chip and
> > manfacturer id.  Then I thought if I'm going to do a driver for this
> > thing, I probably should expose as much of the chip's functionality as I
> > could.   With 98 registers to read and 160+ sysfs entries to create
> > though, I quickly realized it would be impossible to code and maintain
> > the driver using the existing paradigms.  Hence the table driven
> > approach.  The details of the registers and sysfs entries are all in one
> > easy-to-read table.
>
> It isn't your big table of constants in the code (asc7621_params) that I
> have the problem with, it is the need to reverse-index this table at
> runtime which I am calling into question.
>
> I can see why you want to do it this way.  You have a many-to-many
> mapping between device registers and sysfs entries, which is expressed
> in the main table.  You could just walk down that table and read the
> device registers as required, but this would mean reading some registers
> more than once.  Indeed, some of the flags registers would be read many
> times.  And each read over I2C is *very* time-costly.  So you need
> separate lists of all the registers to be read, with duplicates removed
> so that each is only read once.  No controversy there.
>
> What I'm having trouble with is why these lists (of low and high
> priority registers) need to be generated at runtime.  It would certainly
> be nice and elegant if this information could be obtained directly from
> the main asc7621_params table, without iterative re-indexing, but I
> can't think of a way to do it other than by re-designing the driver.
> That's why I suggested simply hard-coding the lists.
>
> I realise you may think that this would wreck the maintainability of
> your main table, but its not as bad as it sounds!

It is if I'm the one maintaining it. :)

> After all, from the
> table I failed to notice where register zero appears in the read lists;
> it is added as a kind of "side effect".  That ambiguity wouldn't have
> arisen if the lists had been in front of me in the source!  And you get
> to save on half a kilobyte of unnecessary array space into the bargain...
>
> Anyway, if I have failed to dissuade you, and you still really, really
> want to persist with the dynamic list creation, then you might want to
> look at the following further points:

I really, really do.

>
> 1.  Declaring a u8[256] as an automatic array on the rather small kernel
> stack is probably a bad idea.
>
> 2.  C doesn't initialise automatic data to zero so you will have to
> memset the asc7621_register_priorities array before use.  (I suspect you
> have been lucky here with the specificity of your "!= 1" check on the
> array contents!)
>
> 3.  You could save a bit of space by putting both the high and low
> priority lists into the same array:  The size of both lists together
> cannot exceed 256.  This doesn't really matter, though.

All taken care of.

>
> > I think at this point the approach will have to stand unless it's going
> > to prevent acceptance of the driver.  If it is going to be an issue I
> > think I'm just going to have to give up on it for a while.  I only have
> > limited windows of time to work on it.
>
> Fair enough.  I don't have any say in the matter, nor would I want to.
> I'm only expressing opinions, which you can take or leave.  Open debate
> applied to open source development.  Thanks for listening! ;-)

No problem.  I appreciate the feedback!

george

>
> Kind regards,
>
> Ken.


[-- Attachment #2: asc7621_3.patch --]
[-- Type: application/octet-stream, Size: 54376 bytes --]

diff -uprN linux-2.6.25.10/Documentation/hwmon/asc7621 linux-2.6.25.10-gtj/Documentation/hwmon/asc7621
--- linux-2.6.25.10/Documentation/hwmon/asc7621	1969-12-31 17:00:00.000000000 -0700
+++ linux-2.6.25.10-gtj/Documentation/hwmon/asc7621	2008-07-08 23:00:47.719045873 -0600
@@ -0,0 +1,297 @@
+Kernel driver asc7621
+==================
+
+Supported chips:
+  * Andigilog aSC7621, aSC7621a
+    Prefix: 'asc7621'
+    Addresses scanned: I2C 0x2c, 0x2d, 0x2e
+    Datasheet: http://www.andigilog.com/downloads/aSC7621_70A06010.pdf
+
+Author:
+		George Joseph
+		
+		
+Description provided by Dave Pivin @ Andigilog:		
+
+Andigilog has both the PECI and pre-PECI versions of the Heceta-6, as
+Intel calls them. Heceta-6e has high frequency PWM and Heceta-6p has
+added PECI and a 4th thermal zone. The Andigilog aSC7611 is the
+Heceta-6e part and aSC7621 is the Heceta-6p part. They are both in
+volume production, shipping to Intel and their subs.
+
+We have enhanced both parts relative to the governing Intel
+specification. First enhancement is temperature reading resolution. We
+have used registers below 20h for vendor-specific functions in addition
+to those in the Intel-specified vendor range.
+
+Our conversion process produces a result that is reported as two bytes.
+The fan speed control uses this finer value to produce a "step-less" fan
+PWM output. These two bytes are "read-locked" to guarantee that once a
+high or low byte is read, the other byte is locked-in until after the
+next read of any register. So to get an atomic reading, read high or low
+byte, then the very next read should be the opposite byte. Our data
+sheet says 10-bits of resolution, although you may find the lower bits
+are active, they are not necessarily reliable or useful externally. We
+chose not to mask them.
+
+We employ significant filtering that is user tunable as described in the
+data sheet. Our temperature reports and fan PWM outputs are very smooth
+when compared to the competition, in addition to the higher resolution
+temperature reports. The smoother PWM output does not require user
+intervention.
+
+We offer GPIO features on the former VID pins. These are open-drain
+outputs or inputs and may be used as general purpose I/O or as alarm
+outputs that are based on temperature limits. These are in 19h and 1Ah.
+
+We offer flexible mapping of temperature readings to thermal zones. Any
+temperature may be mapped to any zone, which has a default assignment
+that follows Intel's specs. 
+
+Since there is a fan to zone assignment that allows for the "hotter" of
+a set of zones to control the PWM of an individual fan, but there is no
+indication to the user, we have added an indicator that shows which zone
+is currently controlling the PWM for a given fan. This is in register
+00h.
+
+Both remote diode temperature readings may be given an offset value such
+that the reported reading as well as the temperature used to determine
+PWM may be offset for system calibration purposes.
+
+PECI Extended configuration allows for having more than two domains per
+PECI address and also provides an enabling function for each PECI
+address. One could use our flexible zone assignment to have a zone
+assigned to up to 4 PECI addresses. This is not possible in the default
+Intel configuration. This would be useful in multi-CPU systems with
+individual fans on each that would benefit from individual fan control.
+This is in register 0Eh.
+
+The tachometer measurement system is flexible and able to adapt to many
+fan types. We can also support pulse-stretched PWM so that 3-wire fans
+may be used. These characteristics are in registers 04h to 07h.
+
+Finally, we have added a tach disable function that turns off the tach
+measurement system for individual tachs in order to save power. That is
+in register 75h.
+
+--
+aSC7621 Product Description
+
+The aSC7621 has a two wire digital interface compatible with SMBus 2.0. 
+Using a 10-bit ADC, the aSC7621 measures the temperature of two remote diode 
+connected transistors as well as its own die. Support for Platform 
+Environmental Control Interface (PECI) is included.  
+
+Using temperature information from these four zones, an automatic fan speed 
+control algorithm is employed to minimize acoustic impact while achieving 
+recommended CPU temperature under varying operational loads. 
+
+To set fan speed, the aSC7621 has three independent pulse width modulation 
+(PWM) outputs that are controlled by one, or a combination of three, 
+temperature zones. Both high- and low-frequency PWM ranges are supported. 
+
+The aSC7621 also includes a digital filter that can be invoked to smooth 
+temperature readings for better control of fan speed and minimum acoustic 
+impact. 
+
+The aSC7621 has tachometer inputs to measure fan speed on up to four fans. 
+Limit and status registers for all measured values are included to alert 
+the system host that any measurements are outside of programmed limits 
+via status registers.
+
+System voltages of VCCP, 2.5V, 3.3V, 5.0V, and 12V motherboard power are 
+monitored efficiently with internal scaling resistors.
+
+Features
+- Supports PECI interface and monitors internal and remote thermal diodes
+- 2-wire, SMBus 2.0 compliant, serial interface
+- 10-bit ADC
+- Monitors VCCP, 2.5V, 3.3V, 5.0V, and 12V motherboard/processor supplies
+- Programmable autonomous fan control based on temperature readings
+- Noise filtering of temperature reading for fan speed control
+- 0.25�C digital temperature sensor resolution
+- 3 PWM fan speed control outputs for 2-, 3- or 4-wire fans and up to 4 fan 
+	tachometer inputs
+- Enhanced measured temperature to Temperature Zone assignment.
+- Provides high and low PWM frequency ranges
+- 3 GPIO pins for custom use
+- 24-Lead QSOP package
+
+Configuration Notes
+===================
+
+Except where noted below, the sysfs entries created by this driver follow
+the standards defined in "sysfs-interface".
+
+temp1_source
+	0 	(default) peci_legacy = 0, Remote 1 Temperature 	 
+			peci_legacy = 1, PECI Processor Temperature 0 	 
+	1 	Remote 1 Temperature 	 
+	2 	Remote 2 Temperature 	 
+	3 	Internal Temperature 	 
+	4 	PECI Processor Temperature 0 	 
+	5 	PECI Processor Temperature 1 	 
+	6 	PECI Processor Temperature 2 	 
+	7   PECI Processor Temperature 3 	 
+
+temp2_source
+	0 	(default) Internal Temperature 	 
+	1 	Remote 1 Temperature 	 
+	2 	Remote 2 Temperature 	 
+	3 	Internal Temperature 	 
+	4 	PECI Processor Temperature 0 	 
+	5 	PECI Processor Temperature 1 	 
+	6 	PECI Processor Temperature 2 	 
+	7 	PECI Processor Temperature 3 	 
+
+temp3_source
+	0 	(default) Remote 2 Temperature
+	1 	Remote 1 Temperature
+	2 	Remote 2 Temperature
+	3 	Internal Temperature
+	4 	PECI Processor Temperature 0
+	5 	PECI Processor Temperature 1
+	6 	PECI Processor Temperature 2
+	7 	PECI Processor Temperature 3
+
+temp4_source
+	0 	(default) peci_legacy = 0, PECI Processor Temperature 0
+			peci_legacy = 1, Remote 1 Temperature
+	1 	Remote 1 Temperature
+	2 	Remote 2 Temperature
+	3 	Internal Temperature
+	4 	PECI Processor Temperature 0
+	5 	PECI Processor Temperature 1
+	6 	PECI Processor Temperature 2
+	7 	PECI Processor Temperature 3
+
+temp[1-4]_smoothing_enable
+temp[1-4]_smoothing_time
+	Smooths spikes in temp readings caused by noise.
+	0	35 sec
+	1	17.6 sec
+	2	11.8 sec
+	3	7.0 sec
+	4	4.4 sec
+	5	3.0 sec
+	6	1.6 sec
+	7	0.8 sec
+
+temp[1-4]_crit
+	When the corresponding zone temperature reaches this value,
+	ALL pwm outputs will got to 100%.
+
+temp[5-8]_input
+temp[5-8]_enable
+	The aSC7621 can also read temperatures provided by the processor
+	via the PECI bus.  Usually these are "core" temps and are relative
+	to the point where the automatical thermal control circuit starts
+	throttling.  This means that these are usually negative numbers.
+
+pwm[1-3]_enable
+	0		Fan off.
+	1		Fan on manual control.
+	2		Fan on automatic control and will run at the minimum pwm
+				if the temperature for the zone is below the minimum.
+	3		Fan on automatic control but will be off if the temperature 
+				for the zone is below the minimum.
+	4-254	Ignored.
+	255		Fan on full.
+	
+pwm[1-3]_auto_channels
+	Bitmap as described in sysctl-interface with the following
+	exceptions...
+	Only the following combinations of zones (and their corresponding masks) 
+	are valid:
+	1			
+	2
+	3
+	2,3
+	1,2,3
+	4
+	1,2,3,4
+
+	Special values:
+	0		Disabled.
+	16		Fan on manual control.
+	31		Fan on full.
+	
+	
+pwm[1-3]_invert
+	When set, inverts the meaning of pwm[1-3].  
+	I.E.  when pwm = 0, the fan will be on full and
+	when pwm = 255 the fan will be off.
+	
+pwm[1-3]_freq
+	PWM frequency in Hz
+	Valid values in Hz are:
+	
+	10
+	15
+	23
+	30  (default)
+	38
+	47
+	62
+	94
+	23000
+	24000
+	25000
+	26000
+	27000
+	28000
+	29000
+	30000
+	
+	Setting any other value will be ignored.
+	
+peci_enable
+	Enables or disables PECI
+	
+peci_avg
+	Input filter averate time.
+	
+	0 	0 Sec. (no Smoothing) (default) 
+	1 	0.25 Sec.
+	2 	0.5 Sec.
+	3 	1.0 Sec.
+	4 	2.0 Sec.
+	5 	4.0 Sec.
+	6 	8.0 Sec.
+	7 	0.0 Sec	
+
+peci_legacy
+
+	0 	Standard Mode (default)
+		Remote Diode 1 reading is associated with
+		Temperature Zone 1, PECI is associated with
+		Zone 4
+	
+	1	Legacy Mode
+		PECI is associated with Temperature Zone 1,
+		Remote Diode 1 is associated with Zone 4
+
+peci_diode
+	Diode filter
+	
+	0	0.25 Sec.
+	1 	1.1 Sec.
+	2 	2.4 Sec.  (default) 
+	3 	3.4 Sec.
+	4 	5.0 Sec.
+	5 	6.8 Sec.
+	6 	10.2 Sec.
+	7 	16.4 Sec.	
+
+peci_4domain
+	Four domain enable
+	
+	0 	1 or 2 Domains for enabled processors (default) 
+	1 	3 or 4 Domains for enabled processors
+
+peci_domain
+	Domain
+	
+	0 	Processor contains a single domain (0) 	 (default)	
+	1 	Processor contains two domains (0,1)
+	
diff -uprN linux-2.6.25.10/drivers/hwmon/asc7621.c linux-2.6.25.10-gtj/drivers/hwmon/asc7621.c
--- linux-2.6.25.10/drivers/hwmon/asc7621.c	1969-12-31 17:00:00.000000000 -0700
+++ linux-2.6.25.10-gtj/drivers/hwmon/asc7621.c	2008-07-08 23:11:36.352206165 -0600
@@ -0,0 +1,1423 @@
+/*
+    asc7621.c - Part of lm_sensors, Linux kernel modules for hardware
+             monitoring
+    Copyright (c) 2007 George Joseph  <george.joseph@fairview5.com>
+
+    Chip details at <http://www.andigilog.com>
+
+ * 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 program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301 USA.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/jiffies.h>
+#include <linux/i2c.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+
+/* Addresses to scan */
+static unsigned short normal_i2c[] = {
+	0x2c, 0x2d, 0x2e, I2C_CLIENT_END
+};
+
+/* 
+ * Insmod parameters
+ * This macro also creates the "chips" enum referenced later.
+ */
+I2C_CLIENT_INSMOD_2(asc7621, asc7621a);
+
+#define INTERVAL_HIGH  (HZ + HZ / 2)
+#define INTERVAL_LOW  (1 * 60 * HZ)
+#define PRI_NONE 0
+#define PRI_LOW 1
+#define PRI_HIGH 2
+#define FIRST_CHIP asc7621
+#define LAST_CHIP asc7621a
+
+struct asc7621_chip {
+	char *name;
+	enum chips chip_type;
+	u8 company_reg;
+	u8 company_id;
+	u8 verstep_reg;
+	u8 verstep_id;
+	unsigned short *addresses;
+};
+/*
+ * The "chips" enum created by I2C_CLIENT_INSMOD_2 has "any_chip" as
+ * the first element in the enum, so it must also be first in our array.
+ */
+static struct asc7621_chip asc7621_chips[] = {
+	{
+	 .name = "any",.chip_type = any_chip,
+	 },
+	{
+	 .name = "asc7621",.chip_type = asc7621,
+	 .company_reg = 0x3e,.company_id = 0x61,
+	 .verstep_reg = 0x3f,.verstep_id = 0x6c,
+	 .addresses = normal_i2c,
+	 },
+	{
+	 .name = "asc7621a",.chip_type = asc7621a,
+	 .company_reg = 0x3e,.company_id = 0x61,
+	 .verstep_reg = 0x3f,.verstep_id = 0x6d,
+	 .addresses = normal_i2c,
+	 },
+};
+
+/* 
+ * Defines the highest register to be used, not the count.
+ * The actual count will probably be smaller because of gaps
+ * in the implementation (unused register locations).
+ * This define will safely set the array size of both the parameter
+ * and data arrays.
+ * This comes from the data sheet register description table.
+ */
+#define LAST_REGISTER 0xff
+
+struct asc7621_data {
+	struct i2c_client client;
+	struct device *class_dev;
+	struct mutex update_lock;
+	int valid;		/* !=0 if following fields are valid */
+	unsigned long last_high_reading;	/* In jiffies */
+	unsigned long last_low_reading;	/* In jiffies */
+
+	/* 
+	 * Registers we care about occupy the corresponding index
+	 * in the array.  Registers we don't care about are left
+	 * at 0.
+	 */
+	u8 reg[LAST_REGISTER + 1];
+};
+
+/* 
+ * Macro to get the parent asc7621_param structure
+ * from a sensor_device_attribute passed into the
+ * show/store functions.
+ */
+#define to_asc7621_param(_sda) \
+	container_of(_sda, struct asc7621_param, sda)
+
+/*
+ * Each parameter to be retrieved needs an asc7621_param structure
+ * allocated.  It contains the sensor_device_attribute structure
+ * and the control info needed to retrieve the value from the register map.
+*/
+struct asc7621_param {
+	struct sensor_device_attribute sda;
+	u8 priority;
+	u8 msb[3];
+	u8 lsb[3];
+	u8 mask[3];
+	u8 shift[3];
+	u8 value;
+	char *label;
+};
+
+/*
+ * This is the map that ultimately indicates whether we'll be
+ * retrieving a register value or not, and at what frequency.
+ */
+static u8 asc7621_register_table[255];
+static int register_high_start;
+static int register_low_start;
+static int register_high_end;
+static int register_low_end;
+
+static struct asc7621_data *asc7621_update_device(struct device *dev);
+
+#define read_byte(reg) (i2c_smbus_read_byte_data(client, reg) & 0xff)
+#define write_byte(reg, data) i2c_smbus_write_byte_data(client, reg, data)
+
+/*******************************************************
+ *         Data Handlers
+ * Each function handles the formatting, storage
+ * and retrieval of like parameters.
+ *
+ *******************************************************/
+
+#define SETUP_SHOW_data_param(d, a) \
+	struct sensor_device_attribute *sda = to_sensor_dev_attr(a); \
+	struct asc7621_data *data = asc7621_update_device(d); \
+	struct asc7621_param *param = to_asc7621_param(sda)
+
+#define SETUP_STORE_data_param(d, a) \
+	struct sensor_device_attribute *sda = to_sensor_dev_attr(a); \
+	struct i2c_client *client = to_i2c_client(d); \
+	struct asc7621_data *data = i2c_get_clientdata(client); \
+	struct asc7621_param *param = to_asc7621_param(sda)
+
+/*
+ * u8 is just what it sounds like...an unsigned byte with no
+ * special formatting.
+ */
+static ssize_t show_u8(struct device *dev, struct device_attribute *attr,
+		       char *buf)
+{
+	SETUP_SHOW_data_param(dev, attr);
+
+	return sprintf(buf, "%u\n", data->reg[param->msb[0]]);
+}
+
+static ssize_t store_u8(struct device *dev, struct device_attribute *attr,
+			const char *buf, size_t count)
+{
+	SETUP_STORE_data_param(dev, attr);
+
+	u8 reqval = simple_strtoul(buf, NULL, 10);
+
+	mutex_lock(&data->update_lock);
+	data->reg[param->msb[0]] = reqval;
+	write_byte(param->msb[0], reqval);
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+/* 
+ * Many of the config values occupy only a few bits of a register.
+ */
+static ssize_t show_bitmask(struct device *dev,
+			    struct device_attribute *attr, char *buf)
+{
+	SETUP_SHOW_data_param(dev, attr);
+
+	return sprintf(buf, "%u\n",
+		       (data->reg[param->msb[0]] >> param->
+			shift[0]) & param->mask[0]);
+}
+
+static ssize_t store_bitmask(struct device *dev,
+			     struct device_attribute *attr,
+			     const char *buf, size_t count)
+{
+	SETUP_STORE_data_param(dev, attr);
+
+	u8 reqval = simple_strtoul(buf, NULL, 10);
+	u8 currval;
+
+	reqval = (reqval & param->mask[0]) << param->shift[0];
+
+	mutex_lock(&data->update_lock);
+	currval = read_byte(param->msb[0]);
+	reqval |= (currval & ~(param->mask[0] << param->shift[0]));
+	data->reg[param->msb[0]] = reqval;
+	write_byte(param->msb[0], reqval);
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+/* 16 bit fan rpm values 
+ *
+ * reported by the device as the number of 11.111us periods (90khz)
+ * between full fan rotations.  Therefore...
+ *
+ * RPM = (90000 * 60) / register value
+ *
+ */
+static ssize_t show_fan16(struct device *dev,
+			  struct device_attribute *attr, char *buf)
+{
+	SETUP_SHOW_data_param(dev, attr);
+
+	u16 regval = (data->reg[param->msb[0]] << 8) | data->reg[param->lsb[0]];
+
+	return sprintf(buf, "%u\n",
+		       (regval == 0 ? -1 : (regval) ==
+			0xffff ? 0 : 5400000 / regval));
+}
+
+static ssize_t store_fan16(struct device *dev,
+			   struct device_attribute *attr, const char *buf,
+			   size_t count)
+{
+	SETUP_STORE_data_param(dev, attr);
+
+	u32 reqval = simple_strtoul(buf, NULL, 10);
+
+	/* If a minimum RPM of zero is requested, then we set the register to
+	   0xffff. This value allows the fan to be stopped completely without
+	   generating an alarm. If any value other than 0 is requested, then
+	   we limit the calculated tacho period to 0xfffe, which equates to a
+	   minimum rotation speed of about 82 RPM. */
+	reqval = (reqval == 0 ? 0xffff :
+		  SENSORS_LIMIT(5400000 / reqval, 0, 0xfffe));
+
+	mutex_lock(&data->update_lock);
+	data->reg[param->msb[0]] = (reqval >> 8) & 0xff;
+	data->reg[param->lsb[0]] = reqval & 0xff;
+	write_byte(param->msb[0], data->reg[param->msb[0]]);
+	write_byte(param->lsb[0], data->reg[param->lsb[0]]);
+	mutex_unlock(&data->update_lock);
+
+	return count;
+}
+
+/* Voltages are scaled in the device so that the nominal voltage is
+ * 3/4ths of the 0-255 range (I.E 192 in the MSB).
+ * If all voltages are 'normal' then all voltage registers will read
+ * 0xC0 in the MSB.
+ *
+ * The tables below scale the voltages readings so that the 3/4 scale
+ * reading maps exactly to the Nominal Voltage for each sensor given in
+ * the datasheet, as follows:
+ *
+ * in0 (2.5V)  : 192 * 625 / 48 =  2500
+ * in1 (2.25V) : 192 * 375 / 32 =  2250
+ * in2 (3.3V)  : 192 * 275 / 16 =  3300
+ * in4 (5V)    : 192 * 625 / 24 =  5000
+ * in5 (12V)   : 192 * 125 /  2 = 12000
+ *
+ * To scale the 10-bit registers, we multiply the divisors by 4.
+ * When writing the limit registers, we interchange multipliers and divisors.
+*/
+
+static s32 asc7621_in_scaling_mul[] = { 625, 375, 275, 625, 125 };
+static s32 asc7621_in_scaling_div[] = { 48, 32, 16, 24, 2 };
+
+static ssize_t show_in10(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	u8 nr;
+	s32 regval;
+	SETUP_SHOW_data_param(dev, attr);
+
+	nr = sda->index;
+
+	/* The LSB value is a 2-bit scaling of the MSB's LSbit value. */
+	regval = (data->reg[param->msb[0]] << 2) +
+	    (data->reg[param->lsb[0]] >> 6);
+
+	regval *= asc7621_in_scaling_mul[nr];
+	regval /= (asc7621_in_scaling_div[nr] * 4);
+
+	return sprintf(buf, "%u\n", regval);
+}
+
+/* 8 bit voltage values (the mins and maxs) */
+static ssize_t show_in8(struct device *dev, struct device_attribute *attr,
+			char *buf)
+{
+	u8 nr;
+	unsigned int regval;
+
+	SETUP_SHOW_data_param(dev, attr);
+
+	nr = sda->index;
+	regval = data->reg[param->msb[0]];
+	regval *= asc7621_in_scaling_mul[nr];
+	regval /= asc7621_in_scaling_div[nr];
+
+	return sprintf(buf, "%u\n", regval);
+}
+
+static ssize_t store_in8(struct device *dev, struct device_attribute *attr,
+			 const char *buf, size_t count)
+{
+
+	u8 nr;
+	s32 reqval;
+
+	SETUP_STORE_data_param(dev, attr);
+
+	nr = sda->index;
+	reqval = simple_strtoul(buf, NULL, 10);
+	reqval *= asc7621_in_scaling_div[nr];
+	reqval /= asc7621_in_scaling_mul[nr];
+	reqval = SENSORS_LIMIT(reqval, 0, 255);
+
+	mutex_lock(&data->update_lock);
+	data->reg[param->msb[0]] = reqval;
+	write_byte(param->msb[0], reqval);
+	mutex_unlock(&data->update_lock);
+
+	return count;
+}
+
+static ssize_t show_temp8(struct device *dev,
+			  struct device_attribute *attr, char *buf)
+{
+	SETUP_SHOW_data_param(dev, attr);
+
+	return sprintf(buf, "%d\n", ((s8) data->reg[param->msb[0]]) * 1000);
+}
+
+static ssize_t store_temp8(struct device *dev,
+			   struct device_attribute *attr, const char *buf,
+			   size_t count)
+{
+	SETUP_STORE_data_param(dev, attr);
+
+	s32 reqval = simple_strtol(buf, NULL, 10);
+	s8 temp = SENSORS_LIMIT((reqval / 1000), -127, 127);
+
+	mutex_lock(&data->update_lock);
+	data->reg[param->msb[0]] = temp;
+	write_byte(param->msb[0], temp);
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+/*
+ * Temperatures that occupy 2 bytes always have the whole
+ * number of degrees in the MSB with some part of the LSB
+ * indicating fractional degrees.
+ */
+
+/*   mmmmmmmm.llxxxxxx */
+static ssize_t show_temp10(struct device *dev,
+			   struct device_attribute *attr, char *buf)
+{
+	SETUP_SHOW_data_param(dev, attr);
+
+	u8 msb = data->reg[param->msb[0]];
+	u8 lsb = (data->reg[param->lsb[0]] >> 6) & 0x03;
+	int temp = (((s8) msb) * 1000) + (lsb * 250);
+
+	return sprintf(buf, "%d\n", temp);
+}
+
+/*   mmmmmm.ll */
+static ssize_t show_temp62(struct device *dev,
+			   struct device_attribute *attr, char *buf)
+{
+	SETUP_SHOW_data_param(dev, attr);
+
+	u8 regval = data->reg[param->msb[0]];
+	u32 temp = ((s8) (regval & 0xfc) * 1000) + ((regval & 0x03) * 250);
+
+	return sprintf(buf, "%d\n", temp);
+}
+
+static ssize_t store_temp62(struct device *dev,
+			    struct device_attribute *attr, const char *buf,
+			    size_t count)
+{
+	SETUP_STORE_data_param(dev, attr);
+
+	s32 i, f;
+	s8 temp;
+
+	s32 reqval = simple_strtol(buf, NULL, 10);
+	reqval = SENSORS_LIMIT(reqval, -32000, 31750);
+	i = reqval / 1000;
+	f = reqval - (i * 1000);
+	temp = i << 2;
+	temp |= f / 250;
+
+	mutex_lock(&data->update_lock);
+	data->reg[param->msb[0]] = temp;
+	write_byte(param->msb[0], temp);
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+/*
+ * The aSC7621 doesn't provide an "auto_point2".  Instead, you
+ * specify the auto_point1 and a range.  To keep with the sysfs
+ * hwmon specs, we synthesize the auto_point_2 from them.
+ */
+
+static u32 asc7621_range_map[] = {
+	2000, 2500, 3330, 4000, 5000, 6670, 8000, 10000,
+	13330, 16000, 20000, 26670, 32000, 40000, 53330, 80000,
+};
+
+static ssize_t show_ap2_temp(struct device *dev,
+			     struct device_attribute *attr, char *buf)
+{
+	SETUP_SHOW_data_param(dev, attr);
+
+	s32 auto_point1 = ((s8) data->reg[param->msb[1]]) * 1000;
+	u8 regval =
+	    ((data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0]);
+	s32 temp =
+	    auto_point1 + asc7621_range_map[SENSORS_LIMIT(regval, 0, 15)];
+
+	return sprintf(buf, "%d\n", temp);
+
+}
+
+static ssize_t store_ap2_temp(struct device *dev,
+			      struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	SETUP_STORE_data_param(dev, attr);
+
+	s32 reqval = simple_strtoul(buf, NULL, 10);
+	s32 auto_point1 = data->reg[param->msb[1]] * 1000;
+	s8 i = 0;
+	u8 currval = 0;
+	u8 newval = 255;
+
+	for (i = ARRAY_SIZE(asc7621_range_map) - 1; i >= 0; i--) {
+		if (reqval >= auto_point1 + asc7621_range_map[i]) {
+			newval = i;
+			break;
+		}
+	}
+	if (newval == 255)
+		return count;
+
+	newval = (newval & param->mask[0]) << param->shift[0];
+
+	mutex_lock(&data->update_lock);
+	currval = read_byte(param->msb[0]);
+	newval |= (currval & ~(param->mask[0] << param->shift[0]));
+	data->reg[param->msb[0]] = newval;
+	write_byte(param->msb[0], newval);
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+static ssize_t show_pwm_ac(struct device *dev,
+			   struct device_attribute *attr, char *buf)
+{
+	SETUP_SHOW_data_param(dev, attr);
+
+	u8 config =
+	    (data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0];
+	u8 altbit =
+	    (data->reg[param->msb[1]] >> param->shift[1]) & param->mask[1];
+	u8 regval = config | (altbit << 3);
+	u8 map[] = {
+		0x01, 0x02, 0x04, 0x1f, 0x00, 0x06, 0x07, 0x10,
+		0x08, 0x0f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f
+	};
+
+	return sprintf(buf, "%u\n", map[SENSORS_LIMIT(regval, 0, 15)]);
+}
+
+static ssize_t store_pwm_ac(struct device *dev,
+			    struct device_attribute *attr,
+			    const char *buf, size_t count)
+{
+	SETUP_STORE_data_param(dev, attr);
+
+	u8 reqval = simple_strtoul(buf, NULL, 10);
+	u8 currval = 0;
+	u8 config = 0;
+	u8 altbit = 0;
+	u8 newval = 0;
+	u16 map[] = {
+		0x04, 0x00, 0x01, 0xff, 0x02, 0xff, 0x05, 0x06,
+		0x08, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f,
+		0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03,
+	};
+
+	if (reqval > 31)
+		return count;
+
+	reqval = map[reqval];
+
+	config = reqval & 0x07;
+	altbit = (reqval >> 3) & 0x01;
+
+	config = (config & param->mask[0]) << param->shift[0];
+	altbit = (altbit & param->mask[1]) << param->shift[1];
+
+	mutex_lock(&data->update_lock);
+	currval = read_byte(param->msb[0]);
+	newval = config | (currval & ~(param->mask[0] << param->shift[0]));
+	newval = altbit | (newval & ~(param->mask[1] << param->shift[1]));
+	data->reg[param->msb[0]] = newval;
+	write_byte(param->msb[0], newval);
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+static ssize_t show_pwm_enable(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	SETUP_SHOW_data_param(dev, attr);
+
+	u8 config =
+	    (data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0];
+	u8 altbit =
+	    (data->reg[param->msb[1]] >> param->shift[1]) & param->mask[1];
+	u8 minoff =
+	    (data->reg[param->msb[2]] >> param->shift[2]) & param->mask[2];
+	u8 val = config | (altbit << 3);
+	u8 newval = 0;
+
+	if (val == 3 || val >= 10)
+		newval = 255;
+	else if (val == 4)
+		newval = 0;
+	else if (val == 7)
+		newval = 1;
+	else if (minoff == 1)
+		newval = 2;
+	else
+		newval = 3;
+
+	return sprintf(buf, "%u\n", newval);
+}
+
+static ssize_t store_pwm_enable(struct device *dev,
+				struct device_attribute *attr,
+				const char *buf, size_t count)
+{
+	SETUP_STORE_data_param(dev, attr);
+
+	u8 reqval = simple_strtoul(buf, NULL, 10);
+	u8 currval = 0;
+	u8 config = 0;
+	u8 altbit = 0;
+	u8 newval = 0;
+	u8 minoff = 255;
+
+	switch (reqval) {
+	case (0):
+		newval = 0x04;
+		break;
+	case (1):
+		newval = 0x07;
+		break;
+	case (2):
+		newval = 0x00;
+		minoff = 1;
+		break;
+	case (3):
+		newval = 0x00;
+		minoff = 0;
+		break;
+	case (255):
+		newval = 0x03;
+		break;
+	default:
+		return count;
+	}
+
+	config = newval & 0x07;
+	altbit = (newval >> 3) & 0x01;
+
+	mutex_lock(&data->update_lock);
+	config = (config & param->mask[0]) << param->shift[0];
+	altbit = (altbit & param->mask[1]) << param->shift[1];
+	currval = read_byte(param->msb[0]);
+	newval = config | (currval & ~(param->mask[0] << param->shift[0]));
+	newval = altbit | (newval & ~(param->mask[1] << param->shift[1]));
+	data->reg[param->msb[0]] = newval;
+	write_byte(param->msb[0], newval);
+	if (minoff < 255) {
+		minoff = (minoff & param->mask[2]) << param->shift[2];
+		currval = read_byte(param->msb[2]);
+		newval =
+		    minoff | (currval & ~(param->mask[2] << param->shift[2]));
+		data->reg[param->msb[2]] = newval;
+		write_byte(param->msb[2], newval);
+	}
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+static u32 asc7621_pwm_freq_map[] = {
+	10, 15, 23, 30, 38, 47, 62, 94,
+	23000, 24000, 25000, 26000, 27000, 28000, 29000, 30000
+};
+
+static ssize_t show_pwm_freq(struct device *dev,
+			     struct device_attribute *attr, char *buf)
+{
+	SETUP_SHOW_data_param(dev, attr);
+
+	u8 regval =
+	    (data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0];
+	regval = SENSORS_LIMIT(regval, 0, ARRAY_SIZE(asc7621_pwm_freq_map) - 1);
+
+	return sprintf(buf, "%u\n", asc7621_pwm_freq_map[regval]);
+}
+
+static ssize_t store_pwm_freq(struct device *dev,
+			      struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	SETUP_STORE_data_param(dev, attr);
+
+	u32 reqval = simple_strtoul(buf, NULL, 10);
+	u8 currval = 0;
+	u8 newval = 255;
+	u32 i;
+
+	for (i = 0; i < ARRAY_SIZE(asc7621_pwm_freq_map); i++) {
+		if (reqval == asc7621_pwm_freq_map[i]) {
+			newval = i;
+			break;
+		}
+	}
+	if (newval == 255)
+		return count;
+
+	newval = (newval & param->mask[0]) << param->shift[0];
+
+	mutex_lock(&data->update_lock);
+	currval = read_byte(param->msb[0]);
+	newval |= (currval & ~(param->mask[0] << param->shift[0]));
+	data->reg[param->msb[0]] = newval;
+	write_byte(param->msb[0], newval);
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+static u32 asc7621_pwm_auto_spinup_map[] =
+    { 0, 100, 250, 400, 700, 1000, 2000, 4000 };
+
+static ssize_t show_pwm_ast(struct device *dev,
+			    struct device_attribute *attr, char *buf)
+{
+	SETUP_SHOW_data_param(dev, attr);
+
+	u8 regval =
+	    (data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0];
+	regval =
+	    SENSORS_LIMIT(regval, 0,
+			  ARRAY_SIZE(asc7621_pwm_auto_spinup_map) - 1);
+
+	return sprintf(buf, "%u\n", asc7621_pwm_auto_spinup_map[regval]);
+
+}
+
+static ssize_t store_pwm_ast(struct device *dev,
+			     struct device_attribute *attr,
+			     const char *buf, size_t count)
+{
+	SETUP_STORE_data_param(dev, attr);
+
+	u16 reqval = simple_strtoul(buf, NULL, 10);
+	u8 currval = 0;
+	u8 newval = 255;
+	u32 i;
+
+	for (i = 0; i < ARRAY_SIZE(asc7621_pwm_auto_spinup_map); i++) {
+		if (reqval == asc7621_pwm_auto_spinup_map[i]) {
+			newval = i;
+			break;
+		}
+	}
+	if (newval == 255)
+		return count;
+
+	newval = (newval & param->mask[0]) << param->shift[0];
+
+	mutex_lock(&data->update_lock);
+	currval = read_byte(param->msb[0]);
+	newval |= (currval & ~(param->mask[0] << param->shift[0]));
+	data->reg[param->msb[0]] = newval;
+	write_byte(param->msb[0], newval);
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+static u32 asc7621_temp_smoothing_time_map[] =
+    { 35000, 17600, 11800, 7000, 4400, 3000, 1600, 800 };
+
+static ssize_t show_temp_st(struct device *dev,
+			    struct device_attribute *attr, char *buf)
+{
+	SETUP_SHOW_data_param(dev, attr);
+
+	u8 regval =
+	    (data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0];
+	regval =
+	    SENSORS_LIMIT(regval, 0,
+			  ARRAY_SIZE(asc7621_temp_smoothing_time_map) - 1);
+
+	return sprintf(buf, "%u\n", asc7621_temp_smoothing_time_map[regval]);
+}
+
+static ssize_t store_temp_st(struct device *dev,
+			     struct device_attribute *attr,
+			     const char *buf, size_t count)
+{
+	SETUP_STORE_data_param(dev, attr);
+
+	u16 reqval = simple_strtoul(buf, NULL, 10);
+	u8 currval;
+	u8 newval = 255;
+	u32 i;
+
+	for (i = 0; i < ARRAY_SIZE(asc7621_pwm_auto_spinup_map); i++) {
+		if (reqval == asc7621_temp_smoothing_time_map[i]) {
+			newval = i;
+			break;
+		}
+	}
+
+	if (newval == 255)
+		return count;
+
+	newval = (newval & param->mask[0]) << param->shift[0];
+
+	mutex_lock(&data->update_lock);
+	currval = read_byte(param->msb[0]);
+	newval |= (currval & ~(param->mask[0] << param->shift[0]));
+	data->reg[param->msb[0]] = newval;
+	write_byte(param->msb[0], newval);
+	mutex_unlock(&data->update_lock);
+	return count;
+}
+
+static ssize_t show_const(struct device *dev,
+			  struct device_attribute *attr, char *buf)
+{
+	struct sensor_device_attribute *sda = to_sensor_dev_attr(attr);
+	struct asc7621_param *param = to_asc7621_param(sda);
+
+	return sprintf(buf, "%d\n", param->value);
+}
+
+static ssize_t show_label(struct device *dev,
+			  struct device_attribute *attr, char *buf)
+{
+	struct sensor_device_attribute *sda = to_sensor_dev_attr(attr);
+	struct asc7621_param *param = to_asc7621_param(sda);
+	if (param->label == NULL)
+		return (0);
+	return sprintf(buf, "%s\n", param->label);
+}
+
+/*   End of data handlers  */
+
+/*  These defines do nothing more than make the table easier
+ *  to read when wrapped at column 80.
+ */
+
+/*
+ * Creates a variable length array inititalizer.
+ * VAA(1,3,5,7) would produce {1,3,5,7}
+ */
+#define VAA(args...) {args}
+
+#define PREADW(name, n, pri, rm, rl, m, s, r) \
+	{.sda = SENSOR_ATTR(name, S_IRUGO, show_##r, NULL, n), \
+		.priority = pri,.msb = {rm, 0xff, 0xff}, .lsb = {rl, 0xff, 0xff}, \
+		.mask[0] = m, .shift[0] = s,}
+
+#define PWRITEW(name, n, pri, rm, rl, m, s, r) \
+	{.sda = SENSOR_ATTR(name, S_IRUGO | S_IWUSR, show_##r, store_##r, n), \
+	  .priority = pri,.msb = {rm, 0xff, 0xff}, .lsb = {rl, 0xff, 0xff}, \
+	  .mask[0] = m, .shift[0] = s,}
+
+#define PREADB(name, n, pri, rm, m, s, r) \
+	{.sda = SENSOR_ATTR(name, S_IRUGO, show_##r, NULL, n), \
+		.priority = pri,.msb = {rm, 0xff, 0xff}, .lsb = {0xff, 0xff, 0xff}, \
+		.mask[0] = m, .shift[0] = s,}
+
+#define PWRITEB(name, n, pri, rm, m, s, r) \
+	{.sda = SENSOR_ATTR(name, S_IRUGO | S_IWUSR, show_##r, store_##r, n), \
+	  .priority = pri,.msb = {rm, 0xff, 0xff}, .lsb = {0xff, 0xff, 0xff}, \
+	  .mask[0] = m, .shift[0] = s,}
+
+/*
+ * PWRITEM assumes that the initializers for the .msb, .lsb, .mask and .shift
+ * were created using the VAA macro.
+ */
+#define PWRITEM(name, n, pri, rm, rl, m, s, r) \
+	{.sda = SENSOR_ATTR(name, S_IRUGO | S_IWUSR, show_##r, store_##r, n), \
+	  .priority = pri,.msb = rm, .lsb = rl, .mask = m, .shift = s,}
+
+#define PCONST(name, n, v) \
+	{.sda = SENSOR_ATTR(name, S_IRUGO, show_const, NULL, n), \
+	  .priority = PRI_LOW, .msb = {0xff, 0xff, 0xff}, \
+	.lsb = {0xff, 0xff, 0xff}, .value = v,}
+
+#define PLABEL(name, n, v) \
+	{.sda = SENSOR_ATTR(name, S_IRUGO, show_label, NULL, n), \
+	  .priority = PRI_LOW, .msb = {0xff, 0xff, 0xff}, \
+	.lsb = {0xff, 0xff, 0xff}, .label = v,}
+
+static struct asc7621_param asc7621_params[] = {
+	PREADW(in0_input, 0, PRI_HIGH, 0x20, 0x13, 0, 0, in10),
+	PREADW(in1_input, 1, PRI_HIGH, 0x21, 0x18, 0, 0, in10),
+	PREADW(in2_input, 2, PRI_HIGH, 0x22, 0x11, 0, 0, in10),
+	PREADW(in3_input, 3, PRI_HIGH, 0x23, 0x12, 0, 0, in10),
+	PREADW(in4_input, 4, PRI_HIGH, 0x24, 0x14, 0, 0, in10),
+
+	PLABEL(in0_label, 0, "in0"),
+	PLABEL(in1_label, 1, "in1"),
+	PLABEL(in2_label, 2, "in2"),
+	PLABEL(in3_label, 3, "in3"),
+	PLABEL(in4_label, 4, "in4"),
+
+	PWRITEB(in0_min, 0, PRI_LOW, 0x44, 0, 0, in8),
+	PWRITEB(in1_min, 1, PRI_LOW, 0x46, 0, 0, in8),
+	PWRITEB(in2_min, 2, PRI_LOW, 0x48, 0, 0, in8),
+	PWRITEB(in3_min, 3, PRI_LOW, 0x4a, 0, 0, in8),
+	PWRITEB(in4_min, 4, PRI_LOW, 0x4c, 0, 0, in8),
+
+	PWRITEB(in0_max, 0, PRI_LOW, 0x45, 0, 0, in8),
+	PWRITEB(in1_max, 1, PRI_LOW, 0x47, 0, 0, in8),
+	PWRITEB(in2_max, 2, PRI_LOW, 0x49, 0, 0, in8),
+	PWRITEB(in3_max, 3, PRI_LOW, 0x4b, 0, 0, in8),
+	PWRITEB(in4_max, 4, PRI_LOW, 0x4d, 0, 0, in8),
+
+	PREADB(in0_alarm, 0, PRI_HIGH, 0x41, 0x01, 0, bitmask),
+	PREADB(in1_alarm, 1, PRI_HIGH, 0x41, 0x01, 1, bitmask),
+	PREADB(in2_alarm, 2, PRI_HIGH, 0x41, 0x01, 2, bitmask),
+	PREADB(in3_alarm, 3, PRI_HIGH, 0x41, 0x01, 3, bitmask),
+	PREADB(in4_alarm, 4, PRI_HIGH, 0x42, 0x01, 0, bitmask),
+
+	PREADW(fan1_input, 0, PRI_HIGH, 0x29, 0x28, 0, 0, fan16),
+	PREADW(fan2_input, 1, PRI_HIGH, 0x2b, 0x2a, 0, 0, fan16),
+	PREADW(fan3_input, 2, PRI_HIGH, 0x2d, 0x2c, 0, 0, fan16),
+	PREADW(fan4_input, 3, PRI_HIGH, 0x2f, 0x2e, 0, 0, fan16),
+
+	PLABEL(fan1_label, 0, "fan1"),
+	PLABEL(fan2_label, 1, "fan2"),
+	PLABEL(fan3_label, 2, "fan3"),
+	PLABEL(fan4_label, 3, "fan4"),
+
+	PWRITEW(fan1_min, 0, PRI_LOW, 0x55, 0x54, 0, 0, fan16),
+	PWRITEW(fan2_min, 1, PRI_LOW, 0x57, 0x56, 0, 0, fan16),
+	PWRITEW(fan3_min, 2, PRI_LOW, 0x59, 0x58, 0, 0, fan16),
+	PWRITEW(fan4_min, 3, PRI_LOW, 0x5b, 0x5a, 0, 0, fan16),
+
+	PREADB(fan1_alarm, 0, PRI_HIGH, 0x42, 0x01, 0, bitmask),
+	PREADB(fan2_alarm, 1, PRI_HIGH, 0x42, 0x01, 1, bitmask),
+	PREADB(fan3_alarm, 2, PRI_HIGH, 0x42, 0x01, 2, bitmask),
+	PREADB(fan4_alarm, 3, PRI_HIGH, 0x42, 0x01, 3, bitmask),
+
+	PREADW(temp1_input, 0, PRI_HIGH, 0x25, 0x10, 0, 0, temp10),
+	PREADW(temp2_input, 1, PRI_HIGH, 0x26, 0x15, 0, 0, temp10),
+	PREADW(temp3_input, 2, PRI_HIGH, 0x27, 0x16, 0, 0, temp10),
+	PREADW(temp4_input, 3, PRI_HIGH, 0x33, 0x17, 0, 0, temp10),
+	PREADW(temp5_input, 4, PRI_HIGH, 0xf7, 0xf6, 0, 0, temp10),
+	PREADW(temp6_input, 5, PRI_HIGH, 0xf9, 0xf8, 0, 0, temp10),
+	PREADW(temp7_input, 6, PRI_HIGH, 0xfb, 0xfa, 0, 0, temp10),
+	PREADW(temp8_input, 7, PRI_HIGH, 0xfd, 0xfc, 0, 0, temp10),
+
+	PCONST(temp1_type, 0, 1),
+	PCONST(temp2_type, 1, 3),
+	PCONST(temp3_type, 2, 2),
+	PCONST(temp4_type, 3, 6),
+	PCONST(temp5_type, 4, 6),
+	PCONST(temp6_type, 5, 6),
+	PCONST(temp7_type, 6, 6),
+	PCONST(temp8_type, 7, 6),
+
+	PLABEL(temp1_label, 0, "cpu"),
+	PLABEL(temp2_label, 1, "internal"),
+	PLABEL(temp3_label, 2, "remote"),
+	PLABEL(temp4_label, 3, "peci 1"),
+	PLABEL(temp5_label, 4, "peci 1"),
+	PLABEL(temp6_label, 5, "peci 2"),
+	PLABEL(temp7_label, 6, "peci 3"),
+	PLABEL(temp8_label, 7, "peci 4"),
+
+	PWRITEB(temp1_min, 0, PRI_LOW, 0x4e, 0, 0, temp8),
+	PWRITEB(temp2_min, 1, PRI_LOW, 0x50, 0, 0, temp8),
+	PWRITEB(temp3_min, 2, PRI_LOW, 0x52, 0, 0, temp8),
+	PWRITEB(temp4_min, 3, PRI_LOW, 0x34, 0, 0, temp8),
+
+	PWRITEB(temp1_max, 0, PRI_LOW, 0x4f, 0, 0, temp8),
+	PWRITEB(temp2_max, 1, PRI_LOW, 0x51, 0, 0, temp8),
+	PWRITEB(temp3_max, 2, PRI_LOW, 0x53, 0, 0, temp8),
+	PWRITEB(temp4_max, 3, PRI_LOW, 0x35, 0, 0, temp8),
+
+	PREADB(temp1_alarm, 0, PRI_HIGH, 0x41, 0x01, 4, bitmask),
+	PREADB(temp2_alarm, 1, PRI_HIGH, 0x41, 0x01, 5, bitmask),
+	PREADB(temp3_alarm, 2, PRI_HIGH, 0x41, 0x01, 6, bitmask),
+	PREADB(temp4_alarm, 3, PRI_HIGH, 0x43, 0x01, 0, bitmask),
+
+	PWRITEB(temp1_source, 0, PRI_LOW, 0x02, 0x07, 4, bitmask),
+	PWRITEB(temp2_source, 1, PRI_LOW, 0x02, 0x07, 0, bitmask),
+	PWRITEB(temp3_source, 2, PRI_LOW, 0x03, 0x07, 4, bitmask),
+	PWRITEB(temp4_source, 3, PRI_LOW, 0x03, 0x07, 0, bitmask),
+	PCONST(temp5_source, 4, 4),
+	PCONST(temp6_source, 5, 5),
+	PCONST(temp7_source, 6, 6),
+	PCONST(temp8_source, 7, 7),
+
+	PWRITEB(temp1_smoothing_enable, 0, PRI_LOW, 0x62, 0x01, 3, bitmask),
+	PWRITEB(temp2_smoothing_enable, 1, PRI_LOW, 0x63, 0x01, 7, bitmask),
+	PWRITEB(temp3_smoothing_enable, 2, PRI_LOW, 0x64, 0x01, 3, bitmask),
+	PWRITEB(temp4_smoothing_enable, 3, PRI_LOW, 0x3c, 0x01, 3, bitmask),
+
+	PWRITEB(temp1_smoothing_time, 0, PRI_LOW, 0x62, 0x07, 0, temp_st),
+	PWRITEB(temp2_smoothing_time, 1, PRI_LOW, 0x63, 0x07, 4, temp_st),
+	PWRITEB(temp3_smoothing_time, 2, PRI_LOW, 0x63, 0x07, 0, temp_st),
+	PWRITEB(temp4_smoothing_time, 3, PRI_LOW, 0x3c, 0x07, 0, temp_st),
+
+	PWRITEB(temp1_auto_point1_temp_hyst, 0, PRI_LOW, 0x6d, 0x0f, 4,
+		bitmask),
+	PWRITEB(temp2_auto_point1_temp_hyst, 1, PRI_LOW, 0x6d, 0x0f, 0,
+		bitmask),
+	PWRITEB(temp3_auto_point1_temp_hyst, 2, PRI_LOW, 0x6e, 0x0f, 4,
+		bitmask),
+	PWRITEB(temp4_auto_point1_temp_hyst, 3, PRI_LOW, 0x6e, 0x0f, 0,
+		bitmask),
+
+	PREADB(temp1_auto_point2_temp_hyst, 0, PRI_LOW, 0x6d, 0x0f, 4,
+	       bitmask),
+	PREADB(temp2_auto_point2_temp_hyst, 1, PRI_LOW, 0x6d, 0x0f, 0,
+	       bitmask),
+	PREADB(temp3_auto_point2_temp_hyst, 2, PRI_LOW, 0x6e, 0x0f, 4,
+	       bitmask),
+	PREADB(temp4_auto_point2_temp_hyst, 3, PRI_LOW, 0x6e, 0x0f, 0,
+	       bitmask),
+
+	PWRITEB(temp1_auto_point1_temp, 0, PRI_LOW, 0x67, 0, 0, temp8),
+	PWRITEB(temp2_auto_point1_temp, 1, PRI_LOW, 0x68, 0, 0, temp8),
+	PWRITEB(temp3_auto_point1_temp, 2, PRI_LOW, 0x69, 0, 0, temp8),
+	PWRITEB(temp4_auto_point1_temp, 3, PRI_LOW, 0x3b, 0, 0, temp8),
+
+	PWRITEM(temp1_auto_point2_temp, 0, PRI_LOW, VAA(0x5f, 0x67, 0xff),
+		VAA(0xff, 0xff, 0xff),
+		VAA(0x0f), VAA(4), ap2_temp),
+	PWRITEM(temp2_auto_point2_temp, 1, PRI_LOW, VAA(0x60, 0x68, 0xff),
+		VAA(0xff, 0xff, 0xff),
+		VAA(0x0f), VAA(4), ap2_temp),
+	PWRITEM(temp3_auto_point2_temp, 2, PRI_LOW, VAA(0x61, 0x69, 0xff),
+		VAA(0xff, 0xff, 0xff),
+		VAA(0x0f), VAA(4), ap2_temp),
+	PWRITEM(temp4_auto_point2_temp, 3, PRI_LOW, VAA(0x3c, 0x3b, 0xff),
+		VAA(0xff, 0xff, 0xff),
+		VAA(0x0f), VAA(4), ap2_temp),
+
+	PWRITEB(temp1_crit, 0, PRI_LOW, 0x6a, 0, 0, temp8),
+	PWRITEB(temp2_crit, 1, PRI_LOW, 0x6b, 0, 0, temp8),
+	PWRITEB(temp3_crit, 2, PRI_LOW, 0x6c, 0, 0, temp8),
+	PWRITEB(temp4_crit, 3, PRI_LOW, 0x3d, 0, 0, temp8),
+
+	PWRITEB(temp5_enable, 4, PRI_LOW, 0x0e, 0x01, 0, bitmask),
+	PWRITEB(temp6_enable, 5, PRI_LOW, 0x0e, 0x01, 1, bitmask),
+	PWRITEB(temp7_enable, 6, PRI_LOW, 0x0e, 0x01, 2, bitmask),
+	PWRITEB(temp8_enable, 7, PRI_LOW, 0x0e, 0x01, 3, bitmask),
+
+	PWRITEB(remote1_offset, 0, PRI_LOW, 0x1c, 0, 0, temp62),
+	PWRITEB(remote2_offset, 1, PRI_LOW, 0x1d, 0, 0, temp62),
+
+	PWRITEB(pwm1, 0, PRI_HIGH, 0x30, 0, 0, u8),
+	PWRITEB(pwm2, 1, PRI_HIGH, 0x31, 0, 0, u8),
+	PWRITEB(pwm3, 2, PRI_HIGH, 0x32, 0, 0, u8),
+
+	PWRITEB(pwm1_invert, 0, PRI_LOW, 0x5c, 0x01, 4, bitmask),
+	PWRITEB(pwm2_invert, 1, PRI_LOW, 0x5d, 0x01, 4, bitmask),
+	PWRITEB(pwm3_invert, 2, PRI_LOW, 0x5e, 0x01, 4, bitmask),
+
+	PWRITEM(pwm1_enable, 0, PRI_LOW, VAA(0x5c, 0x5c, 0x62),
+		VAA(0xff, 0xff, 0xff),
+		VAA(0x07, 0x01, 0x01), VAA(5, 3, 5), pwm_enable),
+	PWRITEM(pwm2_enable, 1, PRI_LOW, VAA(0x5d, 0x5d, 0x62),
+		VAA(0xff, 0xff, 0xff),
+		VAA(0x07, 0x01, 0x01), VAA(5, 3, 6), pwm_enable),
+	PWRITEM(pwm3_enable, 2, PRI_LOW, VAA(0x5e, 0x5e, 0x62),
+		VAA(0xff, 0xff, 0xff),
+		VAA(0x07, 0x01, 0x01), VAA(5, 3, 7), pwm_enable),
+
+	PWRITEM(pwm1_auto_channels, 0, PRI_LOW, VAA(0x5c, 0x5c, 0xff),
+		VAA(0xff, 0xff, 0xff),
+		VAA(0x07, 0x01), VAA(5, 3), pwm_ac),
+	PWRITEM(pwm2_auto_channels, 1, PRI_LOW, VAA(0x5d, 0x5d, 0xff),
+		VAA(0xff, 0xff, 0xff),
+		VAA(0x07, 0x01), VAA(5, 3), pwm_ac),
+	PWRITEM(pwm3_auto_channels, 2, PRI_LOW, VAA(0x5e, 0x5e, 0xff),
+		VAA(0xff, 0xff, 0xff),
+		VAA(0x07, 0x01), VAA(5, 3), pwm_ac),
+
+	PWRITEB(pwm1_auto_point1_pwm, 0, PRI_LOW, 0x64, 0, 0, u8),
+	PWRITEB(pwm2_auto_point1_pwm, 1, PRI_LOW, 0x65, 0, 0, u8),
+	PWRITEB(pwm3_auto_point1_pwm, 2, PRI_LOW, 0x66, 0, 0, u8),
+
+	PWRITEB(pwm1_auto_point2_pwm, 0, PRI_LOW, 0x38, 0, 0, u8),
+	PWRITEB(pwm2_auto_point2_pwm, 1, PRI_LOW, 0x39, 0, 0, u8),
+	PWRITEB(pwm3_auto_point2_pwm, 2, PRI_LOW, 0x3a, 0, 0, u8),
+
+	PWRITEB(pwm1_freq, 0, PRI_LOW, 0x5f, 0x0f, 0, pwm_freq),
+	PWRITEB(pwm2_freq, 1, PRI_LOW, 0x60, 0x0f, 0, pwm_freq),
+	PWRITEB(pwm3_freq, 2, PRI_LOW, 0x61, 0x0f, 0, pwm_freq),
+
+	PREADB(pwm1_auto_zone_assigned, 0, PRI_LOW, 0, 0x03, 2, bitmask),
+	PREADB(pwm2_auto_zone_assigned, 1, PRI_LOW, 0, 0x03, 4, bitmask),
+	PREADB(pwm3_auto_zone_assigned, 2, PRI_LOW, 0, 0x03, 6, bitmask),
+
+	PWRITEB(pwm1_auto_spinup_time, 0, PRI_LOW, 0x5c, 0x07, 0, pwm_ast),
+	PWRITEB(pwm2_auto_spinup_time, 1, PRI_LOW, 0x5d, 0x07, 0, pwm_ast),
+	PWRITEB(pwm3_auto_spinup_time, 2, PRI_LOW, 0x5e, 0x07, 0, pwm_ast),
+
+	PWRITEB(peci_enable, 0, PRI_LOW, 0x40, 0x01, 4, bitmask),
+	PWRITEB(peci_avg, 0, PRI_LOW, 0x36, 0x07, 0, bitmask),
+	PWRITEB(peci_domain, 0, PRI_LOW, 0x36, 0x01, 3, bitmask),
+	PWRITEB(peci_legacy, 0, PRI_LOW, 0x36, 0x01, 4, bitmask),
+	PWRITEB(peci_diode, 0, PRI_LOW, 0x0e, 0x07, 4, bitmask),
+	PWRITEB(peci_4domain, 0, PRI_LOW, 0x0e, 0x01, 4, bitmask),
+
+};
+
+static struct asc7621_data *asc7621_update_device(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct asc7621_data *data = i2c_get_clientdata(client);
+	int i;
+	int reg;
+
+/* 
+ * The asc7621 chips guarantee consistent reads of multi-byte values
+ * regardless of the order of the reads.  No special logic is needed
+ * so we can just read the registers in whatever  order they appear
+ * in the asc7621_params array.
+ */
+
+	mutex_lock(&data->update_lock);
+
+	/* Read all the high priority registers */
+
+	if (!data->valid ||
+	    time_after(jiffies, data->last_high_reading + INTERVAL_HIGH)) {
+
+		for (i = register_high_start; i < register_high_end; i++) {
+			reg = asc7621_register_table[i];
+			data->reg[reg] =
+			    i2c_smbus_read_byte_data(client, reg) & 0xff;
+		}
+		data->last_high_reading = jiffies;
+	}
+
+	/* last_reading */
+	/* Read all the low priority registers. */
+	if (!data->valid ||
+	    time_after(jiffies, data->last_high_reading + INTERVAL_LOW)) {
+
+		for (i = register_low_start; i < register_low_end; i++) {
+			reg = asc7621_register_table[i];
+			data->reg[reg] =
+			    i2c_smbus_read_byte_data(client, reg) & 0xff;
+		}
+		data->last_low_reading = jiffies;
+	}
+	/* last_reading */
+	data->valid = 1;
+
+	mutex_unlock(&data->update_lock);
+
+	return data;
+}
+
+/* Standard detection and initialization below */
+
+/* Helper function that checks if an address is valid
+ * for a particular chip.
+ */
+
+static inline int valid_address_for_chip(int chip_type, int address)
+{
+	int i = 0;
+	for (i = 0; asc7621_chips[chip_type].addresses[i] != I2C_CLIENT_END;
+	     i++) {
+		if (asc7621_chips[chip_type].addresses[i] == address) {
+			return (1);
+		}
+	}
+	return (0);
+}
+
+static void asc7621_init_client(struct i2c_client *client)
+{
+	int value;
+	dev_dbg(&client->dev, "Initializing device\n");
+
+	/* Warn if part was not "READY" */
+
+	value = i2c_smbus_read_byte_data(client, 0x40) & 0xff;
+
+	dev_dbg(&client->dev, "asc7621_REG_READY is: 0x%02x\n", value);
+	if (value & 0x02) {
+		dev_err(&client->dev,
+			"Client (%d,0x%02x) config is locked.\n",
+			i2c_adapter_id(client->adapter), client->addr);
+	};
+	if (!(value & 0x04)) {
+		dev_err(&client->dev, "Client (%d,0x%02x) is not ready.\n",
+			i2c_adapter_id(client->adapter), client->addr);
+	};
+
+	/* Start monitoring */
+
+	value = i2c_smbus_read_byte_data(client, 0x40) & 0xff;
+	/* Try to clear LOCK, Set START, save everything else */
+	value = (value & ~0x02) | 0x01;
+	dev_dbg(&client->dev, "Setting READY to: 0x%02x\n", value);
+	write_byte(0x40, value & 0xff);
+
+}
+
+static struct i2c_driver asc7621_driver;
+
+static int asc7621_detect(struct i2c_adapter *adapter, int address, int kind)
+{
+	int company, verstep;
+	struct i2c_client *client = NULL;
+	struct asc7621_data *data;
+	struct device *dev;
+	int err = 0;
+	int chip_index = 0;
+	int i = 0;
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
+		err = -EBUSY;
+		goto exit;
+	};
+
+	if (!(data = kzalloc(sizeof(struct asc7621_data), GFP_KERNEL))) {
+		err = -ENOMEM;
+		goto exit;
+	}
+
+	client = &data->client;
+	i2c_set_clientdata(client, data);
+	client->addr = address;
+	client->adapter = adapter;
+	client->driver = &asc7621_driver;
+	client->flags = 0;
+	dev = &client->dev;
+
+	if (kind > 0) {
+		if (kind < FIRST_CHIP || kind > LAST_CHIP) {
+			dev_err(&adapter->dev,
+				"Chip kind \"%d\" not handled by this driver.\n",
+				kind);
+			err = -EINVAL;
+			goto exit_free;
+		}
+		strlcpy(client->name, asc7621_chips[kind].name, I2C_NAME_SIZE);
+		dev_dbg(&adapter->dev, "Forced %s device at %d,0x%02x\n",
+			client->name, i2c_adapter_id(client->adapter),
+			client->addr);
+
+	} else {
+		dev_dbg(&adapter->dev,
+			"Autodetecting device at %d,0x%02x ...\n",
+			i2c_adapter_id(adapter), address);
+		kind = 0;
+		for (chip_index = FIRST_CHIP; chip_index <= LAST_CHIP;
+		     chip_index++) {
+
+			if (!valid_address_for_chip(chip_index, address)) {
+				continue;
+			}
+
+			company = i2c_smbus_read_byte_data(client,
+							   asc7621_chips
+							   [chip_index].
+							   company_reg)
+			    & 0xff;
+			verstep =
+			    i2c_smbus_read_byte_data(client,
+						     asc7621_chips
+						     [chip_index].verstep_reg) &
+			    0xff;
+
+			dev_dbg(&adapter->dev,
+				"Checking 0x%02x,0x%02x,0x%02x against 0x%02x,0x%02x\n",
+				address, company, verstep,
+				asc7621_chips[chip_index].company_id,
+				asc7621_chips[chip_index].verstep_id);
+
+			if (company ==
+			    asc7621_chips[chip_index].company_id &&
+			    verstep == asc7621_chips[chip_index].verstep_id) {
+				strlcpy(client->name,
+					asc7621_chips[chip_index].name,
+					I2C_NAME_SIZE);
+				kind = asc7621_chips[chip_index].chip_type;
+				dev_info(&adapter->dev, "  Matched %s\n",
+					 asc7621_chips[chip_index].name);
+				break;
+			}
+			dev_dbg(&adapter->dev, "  Didn't match %s\n",
+				asc7621_chips[chip_index].name);
+		}
+		if (kind <= 0) {
+			dev_err(&adapter->dev,
+				"asc7621 not found at %d,0x%02x.\n",
+				i2c_adapter_id(adapter), address);
+			err = -ENODEV;
+			goto exit_free;
+		}
+		dev_dbg(&adapter->dev,
+			"Detected %s device at %d,0x%02x with"
+			" COMPANY: 0x%02x and VERSTEP: 0x%02x\n",
+			client->name,
+			i2c_adapter_id(client->adapter),
+			client->addr, company, verstep);
+	}
+
+	data->valid = 0;
+	mutex_init(&data->update_lock);
+
+	/* Tell the I2C layer a new client has arrived */
+	if ((err = i2c_attach_client(client))) {
+		goto exit_free;
+	}
+
+	/* Initialize the asc7621 chip */
+	asc7621_init_client(client);
+
+	/* Create the sysfs entries */
+	for (i = 0; i < ARRAY_SIZE(asc7621_params); i++) {
+		if ((err =
+		     device_create_file(dev,
+					&(asc7621_params[i].sda.dev_attr)))) {
+			goto exit_remove;
+		}
+	}
+
+	data->class_dev = hwmon_device_register(&client->dev);
+	if (IS_ERR(data->class_dev)) {
+		err = PTR_ERR(data->class_dev);
+		goto exit_remove;
+	}
+
+	return (0);
+
+      exit_remove:
+	for (i = 0; i < ARRAY_SIZE(asc7621_params); i++) {
+		device_remove_file(dev, &(asc7621_params[i].sda.dev_attr));
+	}
+	i2c_detach_client(client);
+
+      exit_free:
+	kfree(data);
+      exit:
+	return err;
+
+}
+
+static int asc7621_detach_client(struct i2c_client *client)
+{
+	struct asc7621_data *data = i2c_get_clientdata(client);
+	int i;
+
+	hwmon_device_unregister(data->class_dev);
+
+	for (i = 0; i < ARRAY_SIZE(asc7621_params); i++) {
+		device_remove_file(&client->dev,
+				   &(asc7621_params[i].sda.dev_attr));
+	}
+
+	i2c_detach_client(client);
+	kfree(data);
+	return 0;
+}
+
+static int asc7621_attach_adapter(struct i2c_adapter *adapter)
+{
+	if (!(adapter->class & I2C_CLASS_HWMON))
+		return 0;
+
+	return i2c_probe(adapter, &addr_data, asc7621_detect);
+}
+
+static struct i2c_driver asc7621_driver = {
+	.driver = {
+		   .name = "asc7621",
+		   },
+	.attach_adapter = asc7621_attach_adapter,
+	.detach_client = asc7621_detach_client,
+};
+
+static int __init sm_asc7621_init(void)
+{
+	int i, j, msb, lsb;
+	u8 *counted;
+
+	/*
+	 * Collect all the registers needed into a single array.
+	 * This way, if a register isn't actually used for anything, 
+	 * we don't retrieve it.
+	 */
+	if (!(counted = kzalloc(sizeof(u8) * 256, GFP_KERNEL))) {
+		return -ENOMEM;
+	}
+
+	register_high_start = 0;
+	register_high_end = 0;
+	for (i = 0; i < ARRAY_SIZE(asc7621_params); i++) {
+		if (asc7621_params[i].priority != PRI_HIGH)
+			continue;
+		for (j = 0; j < ARRAY_SIZE(asc7621_params[i].msb); j++) {
+			msb = asc7621_params[i].msb[j];
+			if (msb == 0xff)
+				break;
+			if (counted[msb] != 1) {
+				counted[msb] = 1;
+				asc7621_register_table[register_high_end++] =
+				    msb;
+
+			}
+		}
+		for (j = 0; j < ARRAY_SIZE(asc7621_params[i].lsb); j++) {
+			lsb = asc7621_params[i].lsb[j];
+			if (lsb == 0xff)
+				break;
+			if (counted[lsb] != 1) {
+				counted[lsb] = 1;
+				asc7621_register_table[register_high_end++] =
+				    lsb;
+			}
+		}
+	}
+
+	register_low_start = register_high_end;
+	register_low_end = register_low_start;
+
+	for (i = 0; i < ARRAY_SIZE(asc7621_params); i++) {
+		if (asc7621_params[i].priority != PRI_LOW)
+			continue;
+		for (j = 0; j < ARRAY_SIZE(asc7621_params[i].msb); j++) {
+			msb = asc7621_params[i].msb[j];
+			if (msb == 0xff)
+				break;
+			if (counted[msb] != 1) {
+				counted[msb] = 1;
+				asc7621_register_table[register_low_end++] =
+				    msb;
+
+			}
+		}
+		for (j = 0; j < ARRAY_SIZE(asc7621_params[i].lsb); j++) {
+			lsb = asc7621_params[i].lsb[j];
+			if (lsb == 0xff)
+				break;
+			if (counted[lsb] != 1) {
+				counted[lsb] = 1;
+				asc7621_register_table[register_low_end++] =
+				    lsb;
+			}
+		}
+	}
+	printk("Pointers: %d %d %d %d\n", register_high_start,
+	       register_high_end, register_low_start, register_low_end);
+	kfree(counted);
+
+	return i2c_add_driver(&asc7621_driver);
+}
+
+static void __exit sm_asc7621_exit(void)
+{
+	i2c_del_driver(&asc7621_driver);
+}
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("George Joseph");
+MODULE_DESCRIPTION("Andigilog aSC7621 and aSC7621a driver");
+
+module_init(sm_asc7621_init);
+module_exit(sm_asc7621_exit);
diff -uprN linux-2.6.25.10/drivers/hwmon/Kconfig linux-2.6.25.10-gtj/drivers/hwmon/Kconfig
--- linux-2.6.25.10/drivers/hwmon/Kconfig	2008-07-02 21:46:47.000000000 -0600
+++ linux-2.6.25.10-gtj/drivers/hwmon/Kconfig	2008-07-08 22:59:48.975676033 -0600
@@ -202,6 +202,19 @@ config SENSORS_ASB100
 	  This driver can also be built as a module.  If so, the module
 	  will be called asb100.
 
+config SENSORS_ASC7621
+	tristate "Andigilog aSC7621"
+	depends on HWMON && I2C
+	help
+	  If you say yes here you get support for the aSC7621
+	  family of SMBus sensors chip found on most Intel X48, X38, 975, 
+	  965 and 945 desktop boards.  Currently supported chips:
+	  aSC7621
+	  aSC7621a
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called asc7621.
+	  
 config SENSORS_ATXP1
 	tristate "Attansic ATXP1 VID controller"
 	depends on I2C && EXPERIMENTAL
diff -uprN linux-2.6.25.10/drivers/hwmon/Makefile linux-2.6.25.10-gtj/drivers/hwmon/Makefile
--- linux-2.6.25.10/drivers/hwmon/Makefile	2008-07-02 21:46:47.000000000 -0600
+++ linux-2.6.25.10-gtj/drivers/hwmon/Makefile	2008-07-08 23:00:21.817559956 -0600
@@ -27,6 +27,7 @@ obj-$(CONFIG_SENSORS_ADT7470)	+= adt7470
 obj-$(CONFIG_SENSORS_ADT7473)	+= adt7473.o
 obj-$(CONFIG_SENSORS_APPLESMC)	+= applesmc.o
 obj-$(CONFIG_SENSORS_AMS)	+= ams/
+obj-$(CONFIG_SENSORS_ASC7621)	+= asc7621.o
 obj-$(CONFIG_SENSORS_ATXP1)	+= atxp1.o
 obj-$(CONFIG_SENSORS_CORETEMP)	+= coretemp.o
 obj-$(CONFIG_SENSORS_DME1737)	+= dme1737.o
diff -uprN linux-2.6.25.10/MAINTAINERS linux-2.6.25.10-gtj/MAINTAINERS
--- linux-2.6.25.10/MAINTAINERS	2008-07-02 21:46:47.000000000 -0600
+++ linux-2.6.25.10-gtj/MAINTAINERS	2008-07-08 23:04:40.040370062 -0600
@@ -638,6 +638,12 @@ W:	http://sourceforge.net/projects/acpi4
 W:	http://xf.iksaif.net/acpi4asus
 S:	Maintained
 
+ASC7621 HARDWARE MONITOR DRIVER
+P:	George Joseph
+M:	george.joseph@fairview5.com
+L:	lm-sensors@lm-sensors.org
+S:	Maintained
+
 ASUS ASB100 HARDWARE MONITOR DRIVER
 P:	Mark M. Hoffman
 M:	mhoffman@lightlink.com

[-- Attachment #3: Type: text/plain, Size: 153 bytes --]

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2008-07-09  5:23 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2008-07-05 13:37 [lm-sensors] patch: asc7621 driver bug fixes Ken Milmore
2008-07-06  6:15 ` Hans de Goede
2008-07-06 16:49 ` Ken Milmore
2008-07-06 18:03 ` George Joseph
2008-07-06 22:18 ` George Joseph
2008-07-07  6:05 ` George Joseph
2008-07-07 23:05 ` Ken Milmore
2008-07-08  0:07 ` George Joseph
2008-07-08  5:19 ` Hans de Goede
2008-07-08  7:31 ` George Joseph
2008-07-08 23:24 ` Ken Milmore
2008-07-09  5:23 ` George Joseph

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.