[PATCH 1/2 RESEND] hwmon: new driver for SMSC DME1737

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

 



Hi Jean,

Again, thanks for the review.

> > + * Copyright (c) 2007 Juerg Haeflier <juergh at gmail.com>
>
> Typo in your name.

Duh... :-)


> > +static u8 DME1737_REG_IN_LSB[] = {0x87, 0x88, 0x88, 0x87, 0x86, 0x84, 0x84};
> > +static u8 DME1737_REG_IN_LSB_SHR[] = {0, 0, 4, 4, 4, 4, 0};
>
> These could be made const.
>
> This approach doesn't seem very efficient to me. You'll end up reading
> registers 0x84-0x88 twice on every refresh cycle. Given that SMBus
> reads aren't exactly fast and cheap, I'd rather avoid extra reads. I
> suggest that you read the values from these 5 registers in a temporary
> array in the update function, and change DME1737_REG_IN_LSB and
> DME1737_REG_TEMP_LSB to list offsets in this array, rather than
> absolute register addresses. That way, you read each register only once.

Agreed, will fix.


> > +#define DME1737_REG_FAN_MAX(ix)              (0xb4 + (ix))
>
> This last macro appears to be valid only for ix in 4-5? This could be
> documented.

Yes, will clarify.


> > +#define DME1737_REG_PWM_CONFIG(ix)   (0x5c + (ix))
> > +#define DME1737_REG_PWM_MIN(ix)              (0x64 + (ix))
>
> These two only valid for ix in 0-2?

Ditto.


> > +#define DME1737_REG_PWM_FREQ(ix)     ((ix) < 3 ? 0x5f + (ix) \
> > +                                               : 0xa3 + (ix))
> > +#define DME1737_REG_PWM_RR(ix)               (0x62 + (ix))
>
> The register mapping for this one is quite different from the rest, it
> is kinda error-prone to present is as if it were similar.

Will clarify.


> > +#define DME1737_REG_ZONE_LOW(ix)     (0x67 + (ix))
> > +#define DME1737_REG_ZONE_ABS(ix)     (0x6a + (ix))
> > +#define DME1737_REG_ZONE_HYST(ix)    (0x6d + (ix))
>
> Again this one is mapped differently, this deserves a comment.

Ditto.


> > +/* ---------------------------------------------------------------------
> > + * Super-I/O constants
> > + * --------------------------------------------------------------------- */
> > +
> > +#define DME1737_COMPANY_SMSC 0x5c
> > +#define DME1737_VERSTEP              0x88
> > +#define DME1737_VERSTEP_MASK 0xf8
>
> These are not Super-I/O constants, as you are comparing them with
> values read from the SMBus registers.

Will change the comment.


> > +     u16 temp[3];
> > +     u8  temp_min[3];
> > +     u8  temp_max[3];
> > +     u8  temp_offset[3];
>
> You should be able to simplify the code in TEMP_FROM_REG and
> TEMP_TO_REG significantly by defining these temperature registers as
> s16 and s8.

I'll look into this.


> > +     u8  config;
> > +     u8  vrm;
>
> For what it's worth, vrm isn't actually a register value.

True.


> > +     u8  zone_abs[3];
> > +     u8  zone_hyst[2];
> > +     u32 alarms;
> > +     u8  vid;
> > +};
>
> You could move alarms either at the beginning or at the end of the
> register list, to minimize holes in the structure.

Good point, will do.


> > +
> > +/* nominal voltage value */
> > +static int IN_NOMINAL(int ix)
> > +{
> > +     int nom[] = {5000, 2250, 3300, 5000, 12000, 3300, 3300};
>
> This array should be declared static const. Same for similar arrays in
> the other conversion helpers. Alternatively, at least for this
> function, you could define a global static array directly, without a
> wrapper function, as the wrapper function doesn't add much.

OK, will optimize.


> > +     return nom[ix];
> > +}
>
>
> You really want to declare all these one- or two-line conversion
> functions inline. This will make your code faster _and_ smaller.

OK.


> > +static int IN_TO_REG(int val, int ix, int res)
> > +{
> > +     return SENSORS_LIMIT(val * (3 << (res - 2)) / IN_NOMINAL(ix),
> > +                          0, (1 << res) - 1);
> > +}
>
> Can you please add proper rounding to these voltage conversion
> formulae? Also, the second function is only ever called with res = 8 so
> you could simplify it a bit.

Will add rounding and simplify the functions that don't require
different resolutions.


> > +static int TEMP_RANGE_TO_REG(int val, int reg)
> > +{
> > +     int range = ((val > 66666) ? 15 : (val > 46666) ? 14 :
> > +                  (val > 36000) ? 13 : (val > 29333) ? 12 :
> > +                  (val > 23333) ? 11 : (val > 18000) ? 10 :
> > +                  (val > 14666) ? 9 : (val > 11666) ? 8 :
> > +                  (val > 9000) ? 7 : (val > 7333) ? 6 :
> > +                  (val > 5833) ? 5 : (val > 4500) ? 4 :
> > +                  (val > 3666) ? 3 : (val > 2917) ? 2 :
> > +                  (val > 2250) ? 1 : 0);
>
> This would be much nicer implemented as a loop iterating over a static
> array.

OK.


> > +static int TEMP_HYST_FROM_REG(int reg, int ix) {
>
> Improperly placed opening curly brace.

OK.


> > +static int FAN_FROM_REG(int reg, int tpc)
> > +{
> > +     return ((reg == 0 || reg == 0xffff) ? 0 :
> > +             (tpc == 0) ? 90000 * 60 / reg : tpc * reg);
> > +}
> > +
> > +static int FAN_TO_REG(int val, int tpc)
> > +{
> > +     return SENSORS_LIMIT((tpc == 0) ? 90000 * 60 / val : val / tpc,
> > +                          0, 65535);
> > +}
>
> It's a bit inconsistent to use 0xffff in one function and 65535 in the
> other.

Will fix.


> > +static int FAN_TYPE_FROM_REG(int reg) {
>
> Improperly placed opening curly brace.

OK.


> > +static int FAN_MAX_TO_REG(int val)
> > +{
> > +     return ((val > 5500) ? 0x0e : (val > 5000) ? 0x0f :
> > +             (val > 4500) ? 0x11 : (val > 4000) ? 0x12 :
> > +             (val > 3500) ? 0x15 : (val > 3000) ? 0x18 :
> > +             (val > 2500) ? 0x1c : (val > 2000) ? 0x21 :
> > +             (val > 1500) ? 0x2a : (val > 1000) ? 0x38 : 0x54);
> > +}
>
> Here again, a loop iterating over a static array (shared between the two
> functions) would be nicer IMHO.

OK.


> > +static int PWM_EN_FROM_REG(int reg)
> > +{
> > +     int en[] = {2, 2, 2, 0, -1, 2, 2, 1};
> > +     return en[(reg >> 5) & 0x07];
> > +}
>
> "-1" isn't a valid value according to our sysfs interface standard. A
> stopped fan would be presented as pwmN_enable = 1 and pwmN = 0
> according to our standard.

OK, will fix.


> > +static int PWM_RR_TO_REG(int val, int ix, int reg)
> > +{
> > +     int rr = ((val > 155) ? 8 : (val > 86) ? 9 :
> > +               (val > 55) ? 10 : (val > 33) ? 11 :
> > +               (val > 22) ? 12 : (val > 14) ? 13 :
> > +               (val > 14) ? 14 : (val > 0) ? 15 : 0);
>
> Bug here, you're doing the same comparison twice. According to the
> array above - which you could have reused) the second (val > 14) should
> be (val > 7).

Good catch, will fix.


> > +     /* enable a Vbat monitoring cycle every 30 secs */
> > +     if (time_after(jiffies, data->last_vbat + 30 * HZ) || !data->valid) {
> > +             dme1737_write(client, DME1737_REG_CONFIG, dme1737_read(client,
> > +                                             DME1737_REG_CONFIG) | 0x10);
> > +             data->last_vbat = jiffies;
> > +     }
>
> Do we really need a new battery reading every 30 seconds? I think I'd
> go for a much larger interval to not draw too much power from the
> battery.

How large? 5min?


> > +             data->config = dme1737_read(client, DME1737_REG_CONFIG);
>
> You only use the value of this register at device initialization time,
> so you don't need to refresh it. Make this a local variable in
> dme1737_init_client(), it's sufficient.

That's a leftover. Will remove.


> > +                     data->in[ix] |= (dme1737_read(client,
> > +                                     DME1737_REG_IN_LSB[ix]) >>
> > +                                     DME1737_REG_IN_LSB_SHR[ix]) & 0x0f;
>
> I would welcome a comment explaining that it is important to read the
> MSB first for integrity.

Will do.


> > +             /* fan registers */
> > +             for (ix = 0; ix < ARRAY_SIZE(data->fan); ix++) {
>
> According to the datasheet, fans 5 and 6 (A and B in the datasheet)
> share their pins with other functions, and are not even the default.
> However you create the fan[5-6] and pwm[5-6] files unconditionally?
> Ideally you should check run-time registers 0x43 to 0x46 and only create
> the sysfs files (and read the registers) if the chip is configured that
> way.

I'll look into this.


> > +                     data->fan[ix] = dme1737_read(client,
> > +                                     DME1737_REG_FAN(ix));
> > +                     data->fan[ix] |= (dme1737_read(client,
> > +                                     DME1737_REG_FAN(ix) + 1) << 8);
>
> I would welcome a comment explaining that it is important to read the
> LSB first for value integrity. Have you tested if the DME1737 supports
> word reads on these registers? This would speed up the update a bit.

I'll check.


> > +                     data->fan_opt[ix] = dme1737_read(client,
> > +                                     DME1737_REG_FAN_OPT(ix));
> > +                     if (ix > 4) {
> > +                             data->fan_max[ix - 5] = dme1737_read(client,
> > +                                     DME1737_REG_FAN_MAX(ix));
> > +                     }
>
> This looks broken to me. Given that ix is in range 0 to 5, shouldn't
> this be "if (ix  >= 4)" and "data->fan_max[ix - 4]"?

Yeah, certainly is broken.


> > +                     if (ix < 2) {
> > +                             data->pwm_rr[ix] = dme1737_read(client,
> > +                                             DME1737_REG_PWM_RR(ix));
> > +                     }
>
> The "ix" in the last statement doesn't have the same meaning as in the
> rest of the loop, that's a bit confusing. Would you mind moving this
> last statement out of the loop?

Will do.


> > +                     if (ix < 2) {
> > +                             data->zone_hyst[ix] = dme1737_read(client,
> > +                                             DME1737_REG_ZONE_HYST(ix));
> > +                     }
>
> Same here.

Ditto.


> > +             data->alarms = dme1737_read(client,
> > +                                             DME1737_REG_ALARM1);
>
> If I read the datasheet properly, you could check the value of bit 7
> and skip the next two reads if it's clear.

I'll check.


> > +     case SYS_FAN_MAX:
> > +             /* only valid for fan[5-6] */
> > +             data->fan_max[ix - 5] = FAN_MAX_TO_REG(val);
> > +             dme1737_write(client, DME1737_REG_FAN_MAX(ix),
> > +                           data->fan_max[ix - 5]);
>
> Again I believe these should be "[ix - 4]".

Yes.


> > +     case SYS_FAN_TYPE:
> > +             /* only valid for fan[1-4] */
> > +             if (!(val == 2 || val == 3 || val == 5 || val == 9)) {
> > +                     count = -EINVAL;
> > +                     dev_warn(&client->dev, "Fan type value %ld not "
> > +                              "supported. Choose one of 2, 3, "
> > +                              "5, or 9.\n", val);
> > +                     goto exit;
> > +             }
> > +             data->fan_opt[ix] = FAN_TYPE_TO_REG(val, dme1737_read(client,
> > +                                     DME1737_REG_FAN_OPT(ix)));
>
> This isn't very efficient. You are doing again the same comparisons in
> FAN_TYPE_TO_REG() as you have been doing above. In general, when you
> need to validate discrete values from user input, it's better to not
> move the conversion to a separate function, so that you can do the
> validation and the conversion in one pass. Look at set_fan_div() in the
> fscher driver for example.

I'll check fscher.


> > +             if (PWM_EN_FROM_REG(data->pwm_config[ix]) != 1) {
> > +                     count = -EPERM;
> > +                     dev_warn(&client->dev, "Attribute is read-only. "
> > +                              "Pwm%d is not in manual mode\n", ix + 1);
> > +                     goto exit;
> > +             }
>
> What about actually turning the sysfs file read-only in this case? The
> f71805f driver does this, I think it's more elegant. An application can
> see that a file has been turned read-only, while an application won't
> read the system log.

I was thinking of going this way initially but dismissed the idea for
being to complicated. I'll revisit it.


> > +             if (val < -1 || val > 2) {
> > +                     count = -EINVAL;
> > +                     dev_warn(&client->dev, "PWM enable %ld not "
> > +                              "supported. Choose one of -1, 0, 1, or 2.\n",
> > +                              val);
> > +                     goto exit;
> > +             }
>
> As said earlier, -1 isn't actually an acceptable value.

Yes, will fix.


> > +             /* refresh the cache */
> > +             data->pwm_config[ix] = dme1737_read(client,
> > +                                             DME1737_REG_PWM_CONFIG(ix));
> > +             if (val == 2 &&
> > +                 PWM_EN_FROM_REG(data->pwm_config[ix]) != 2) {
> > +                     /* turn on auto mode using the saved temp channel
> > +                      * assignment */
> > +                     data->pwm_config[ix] = PWM_ACT_TO_REG(
> > +                                                     data->pwm_act[ix],
> > +                                                     data->pwm_config[ix]);
> > +                     dme1737_write(client, DME1737_REG_PWM_CONFIG(ix),
> > +                                   data->pwm_config[ix]);
> > +             } else if (val != 2) {
> > +                     /* set manual mode */
> > +                     if (PWM_EN_FROM_REG(data->pwm_config[ix]) == 2) {
> > +                             /* save the current temp channel assignment
> > +                              * if we are currently in auto mode */
> > +                             data->pwm_act[ix] = PWM_ACT_FROM_REG(
> > +                                                     data->pwm_config[ix]);
> > +                     }
> > +                     data->pwm_config[ix] = PWM_EN_TO_REG(val,
> > +                                                     data->pwm_config[ix]);
> > +                     dme1737_write(client, DME1737_REG_PWM_CONFIG(ix),
> > +                                   data->pwm_config[ix]);
> > +             }
>
> You should be able to optimize/clean up this a bit:
> * Split the first test to two nested ifs, then you don't need to test
> if "val != 2", a simple "else" will do.
> * You can move the dme1737_write() call outside of the conditional
> blocks, as it's common to both blocks (worst case, you write the same
> value that was already there, which is OK.)

OK, will look into this.


> > +     case SYS_PWM_RAMP_RATE:
> > +             /* only valid for pwm[1-3] */
> > +             data->pwm_rr[ix > 0] = PWM_RR_TO_REG(val, ix,
> > +                                             dme1737_read(client,
> > +                                             DME1737_REG_PWM_RR(ix > 0)));
> > +             dme1737_write(client, DME1737_REG_PWM_RR(ix > 0),
> > +                           data->pwm_rr[ix > 0]);
>
> The datasheet says that ramp rate should be disabled in manual mode,
> but you driver doesn't do that?

I'm not aware of this requirement. I'll check the datasheet.


> > +static ssize_t show_alarms(struct device *dev,
> > +                        struct device_attribute *attr, char *buf)
> > +{
> > +     struct dme1737_data *data = dme1737_update_device(dev);
> > +
> > +     return sprintf(buf, "%d\n", data->alarms);
> > +}
>
> We don't want such compound alarms file in the new drivers. We're
> keeping them in older drivers only for backward compatibility purposes.

OK, will remove it.


> > +     SENSOR_ATTR_2(temp##ix##_auto_point2_temp_crit, S_IRUGO | S_IWUSR, \
> > +             show_temp, set_temp, \
> > +             SYS_TEMP_AUTO_POINT2_TEMP_CRIT, ix-1)
>
> We don't have this in our standard interface. This would rather be
> temp##ix##_auto_point3_temp, and pwm##ix##_auto_point3_pwm hard-coded
> to 255.

OK, will fix.


> > +     SENSOR_ATTR_2(pwm##ix##_ramp_rate, S_IRUGO | S_IWUSR, \
> > +             show_pwm, set_pwm, SYS_PWM_RAMP_RATE, ix-1), \
>
> I am curious, is this parameter really useful in practice?

Not sure :-) One can modify it through the BIOS (on my pundit) so I
thought it's worth exposing it through the driver as well.


> > +     /* force monitoring */
> > +     if (force_start) {
> > +             dme1737_write(client, DME1737_REG_CONFIG,
> > +                     dme1737_read(client, DME1737_REG_CONFIG) | 0x01);
> > +     }
> > +
> > +     data->config = dme1737_read(client, DME1737_REG_CONFIG);
> > +     /* inform if part is not monitoring/started */
> > +     if (!(data->config & 0x01)) {
> > +             dev_err(&client->dev, "Device is not monitoring. Use the "
> > +                     "force_start load parameter to override.\n");
> > +             return -EFAULT;
> > +     }
>
> This construct is rather confusing. When force_start is set, you read
> the configuration register twice, and you test a bit you just set
> yourself. What about this instead:
>
>         data->config = dme1737_read(client, DME1737_REG_CONFIG);
>         /* inform if part is not monitoring/started */
>         if (!(data->config & 0x01)) {
>                 if (!force_start) {
>                         dev_err(&client->dev, "Device is not monitoring. "
>                                 "Use the force_start load parameter to "
>                                 "override.\n");
>                         return -EFAULT;
>                 }
>
>                 /* force monitoring */
>                 data->config |= 0x01;
>                 dme1737_write(client, DME1737_REG_CONFIG, data->config);
>         }

Sure will fix.


> > +     /* inform if part is not ready or locked */
> > +     if (!(data->config & 0x04)) {
> > +             dev_err(&client->dev, "Device is not ready.\n");
> > +             return -EFAULT;
> > +     }
> > +     if (data->config & 0x02) {
> > +             dev_info(&client->dev, "Device is locked.\n");
> > +     }
>
> Shouldn't all sysfs files be switched to read-only in this case?

Yes, will look into this.


> > +     /* inform if fan-to-pwm mapping differs from the default */
> > +     if (reg != 0xa4) {
> > +             dev_err(&client->dev, "Unsupported fan to pwm mapping "
> > +                     "(fan1->pwm%d, fan2->pwm%d, fan3->pwm%d, "
> > +                     "fan4->pwm%d). Please report to the driver "
> > +                     "maintainer.\n",
> > +                     (reg & 0x03) + 1, ((reg >> 4) & 0x03) + 1,
> > +                     ((reg >> 8) & 0x03) + 1, ((reg >> 12) & 0x03) + 1);
> > +             return -EFAULT;
> > +     }
>
> Why would this be a problem? I don't see where your driver is assuming
> anything about this mapping. It might be a good thing to report the
> mapping in the logs in any case, for the interested users. Or maybe we
> need a new sysfs file for this...

It becomes confusing. In case of a non-standard (non 1:1) mapping, one
might manually change pwmX and expect fanX_input to change accordingly
but instead fanY_input changes.


> > +     struct i2c_client *new_client = NULL;
>
> Please rename this to just "client". You don't need to initialize it to
> NULL, BTW.

OK.


> > +     /* For now, we assume we have a valid client. We now create the
> > +      * client structure, even though we cannot fill it completely yet. */
>
> Meaningless legacy comment, please discard.

OK.


> > +     new_client->addr    = address;
> > +     new_client->adapter = adapter;
> > +     new_client->driver  = &dme1737_driver;
> > +     new_client->flags   = 0;
>
> No alignment here please. And flags is already set to 0 by kzalloc
> above.

OK.


> > +     if (!((company == DME1737_COMPANY_SMSC) &&
> > +           ((verstep & DME1737_VERSTEP_MASK) == DME1737_VERSTEP))) {
> > +             err = -ENODEV;
> > +             goto exit_kfree;
> > +     }
>
> You could use a few more registers to make the detection more reliable,
> as we did in sensors-detect.
>
> Also, you should skip this detection step if kind >= 0 (user used a
> force module parameter.)

OK, will do.


> > +     data->valid = 0;
>
> This one too is already initialized by kzalloc.

OK.


> > +     for (i = 0; i < ARRAY_SIZE(dme1737_sysfs_misc); i++) {
> > +             if ((err = device_create_file(&new_client->dev,
> > +                             &dme1737_sysfs_misc[i]))) {
> > +                     goto exit_remove;
> > +             }
> > +     }
>
> Why don't you use sysfs groups instead? sysfs_create_group() and
> sysfs_remove_group() work well, and you can even put all the attributes
> in one group.

I'll look into it.


> > +     .id             = I2C_DRIVERID_DME1737,
>
> Not needed.

OK.


> > +       If you say yes here you get support for the hardware monitoring
> > +       and fan control features of the SMSC DME1737 and ASUS A8000
> > +       Super-I/O chips.
>
> As far as I know, the SMSC SCH5027D is compatible as well. At least it
> is listed as such on our Devices wiki page.

I wouldn't know. Shall I add it blindly?


> ASUS should be spelled Asus, it's not an acronym AFAIK.

OK.


> > +#define I2C_DRIVERID_DME1737 1049
>
> You simply don't need this ID - they will all be removed soon anyway.
> Leave it to 0 in the driver.

OK.


> Nice driver overall, good job! Please provide an updated patch (or
> incremental patch on top of this one, at your option) for upstream
> inclusion.

Thanks. I learned from the master :-)
It'll take me a while to come up with a new patch. I disassembled the
box for noise reduction.

...juerg


> --
> Jean Delvare
>




[Index of Archives]     [Linux Kernel]     [Linux Hardware Monitoring]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]

  Powered by Linux