[lm-sensors] [PATCH] hwmon: Add w83791d support

Jean Delvare khali at linux-fr.org
Wed Apr 5 10:58:20 CEST 2006


Hi Charles,

> Add support for the w83791d sensor chip. The w83791d hardware is
> somewhere between the w83781d and the w83792d and this driver code
> is derived from the code that supports those chips.

Sorry for having been rather silent and slow about your driver to date.
I can see that Greg KH and Rudolf Marek did some review on your code
already, so it should be quite good already, and hopefully not far from
being mergeable.

Here are my own comments.

> +Author: Charles Spirakis
> +Contact: bezaur at gmail.com

Put it all on one line please (makes it easier to cut'n'paste into an
e-mail client to contact the author.)

> +static int init;
> +module_param(init, bool, 0);
> +MODULE_PARM_DESC(init, "Set to one to force chip initialization");

This parameter would be better named "reset", as you still do some
initialization regardless of it being set to 0. What really changes is
the reset step.

> +#define IN_TO_REG(nr,val)	(SENSORS_LIMIT((((val) * 10 + 8) / 16), 0, 255))
> +#define IN_FROM_REG(nr,val)	(((val) * 16) / 10)
> (...)
> +	return sprintf(buf,"%ld\n", \
> +			(long)(IN_FROM_REG(nr, (data->reg[nr] * 10)))); \
> (...)
> +	val = simple_strtoul(buf, NULL, 10) / 10; \
> +	 \
> +	mutex_lock(&data->update_lock); \
> +	data->in_##reg[nr] = SENSORS_LIMIT(IN_TO_REG(nr, val), 0, 255); \

This is no good, you're losing resolution by doing opposite operations
inside and outside the macros. Please simplify it all.

> +/* for temp1 only */
> +#define TEMP1_FROM_REG(val)	(((val) & 0x80 ? \
> +					(val) - 0x100 : \
> +					(val)) * 1000)
> +#define TEMP1_TO_REG(val)	(SENSORS_LIMIT(((val) < 0 ? \
> +					(val) + (0x100 * 1000) : \
> +					(val)) / 1000, 0, 0xff))

What you are doing here with your negative value check and offset
addition, is converting from u8 to s8 and vice-versa. You can let the
hardware do it for you, this will be both more readable and more
efficient. All you have to do is declare w83791d_data.temp1 as an array
of s8 instead of u8 (see the lm90 driver for an example.)

Same goes for temp2 and temp3, except that this is a bit more complex
due to the additional resolution bit. Usually we store each temperature
value in an s16 (left padded, 7 LSB are always 0) in this case.

> +#define PWM_FROM_REG(val)		(val)
> +#define PWM_TO_REG(val)			(SENSORS_LIMIT((val),0,255))

You are not using these for now, so you shouldn't define them.

> #define BEEP_ENABLE_TO_REG(val)		((val) ? 1 : 0)
> #define BEEP_ENABLE_FROM_REG(val)	((val) ? 1 : 0)

These ones are not especially useful, at least the way they are
implemented. They would make sense if you had no separate
w83791d_data.beep_enable member, but you do. So, please either drop
that separate member and keep the macros (with some changes, obviously),
or drop the macros.

> +static u8 div_to_reg(long val)
> +{
> +	int i;
> +	val = SENSORS_LIMIT(val, 1, 128) >> 1;
> +	for (i = 0; i < 6; i++) {
> +		if (val == 0)
> +			break;
> +		val >>= 1;
> +	}
> +	return (u8) i;
> +}

This code has a bug which was fixed in the w83792d driver recently: fan
clock divider value of 128 can't be set, because the end condition
should be "i < 7" instead of "i < 6". Please fix.

http://www.kernel.org/git/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=9632051963cb6e6f7412990f8b962209b9334e13

In a more general way, you may want to take a look at the recent
changes which were made to the w83792d driver, and see if they could
apply to your code too.

+struct w83791d_data {
+	struct i2c_client client;
+	struct class_device *class_dev;
+	struct mutex update_lock;
+	enum chips type;

You don't use the type value anywhere at the moment, and the chances
that your driver will have to support more chips in the future are low,
so you could drop it to save some memory.

> +#define show_fan_reg(reg) \
> +static ssize_t show_##reg(struct device *dev, struct device_attribute *attr, \
> +				char *buf) \
> +{ \
> +	struct sensor_device_attribute *sensor_attr = \
> +						to_sensor_dev_attr(attr); \
> +	int nr = sensor_attr->index - 1; \

You could define the sysfs files attribute differently so that you
don't need to apply that offset on every access to every file. That
would be both shorter and more efficient.

> +	default:
> +		dev_warn(dev, "store_fan_div: Unexpected nr seen: %d\n", nr);
> +		count = -EINVAL;
> +		goto err_exit;

This is so unexpected that this just cannot happen, unless you have an
internal (code) error. So this part should either not exist at all (now
that the driver is working) or be only compiled in in debug mode.

> +	tmp_fan_div = ((data->fan_div[nr] << new_shift) & (~keep_mask));

The outermost pair of parentheses could be omitted.

> +	for (i = 0; i < 3; i++) {
> +		w83791d_write(client, W83791D_REG_BEEP_CTRL[i], val);
> +		val >>= 8;
> +	}

I think Rudolf told you to omit the "& 0xff" masking... but I don't
agree. I think the code is much clearer with the masking, and less
likely to break on future changes too. So feel free to add the mask
back.

> +static ssize_t store_vrm_reg(struct device *dev,
> +				struct device_attribute *attr,
> +				const char *buf, size_t count)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct w83791d_data *data = i2c_get_clientdata(client);
> +	u32 val;
> +
> +	val = simple_strtoul(buf, NULL, 10);
> +	mutex_lock(&data->update_lock);
> +	data->vrm = val;
> +	mutex_unlock(&data->update_lock);
> +
> +	return count;
> +}

You don't actually need to lock here. vrm is internal to the driver,
it's not read from a chip register.

> +	(*sub_cli) = sub_client =
> +			kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
> (...)
> +	sub_client->flags = 0;

Not needed, as kzalloc already zeroed the whole structure.

> +	new_client->flags = 0;

Ditto. Also, I would appreciate if you could rename "new_client" to
just "client" in the detect function. I wish the first driver did not
use "new_client", so we wouldn't have it in all drivers now...

> +	data->valid = 0;

And here too.

> +	if (kind < 0) {
> +		if (w83791d_read(new_client, W83791D_REG_CONFIG) & 0x80) {
> +			dev_dbg(dev, "Detection failed at step 3\n");
> +			goto error1;
> +		}
> +		val1 = w83791d_read(new_client, W83791D_REG_BANK);
> +		val2 = w83791d_read(new_client, W83791D_REG_CHIPMAN);
> +		/* Check for Winbond ID if in bank 0 */
> +		if (!(val1 & 0x07)) {
> +			/* yes it is Bank0 */
> +			if (((!(val1 & 0x80)) && (val2 != 0xa3)) ||
> +			    ((val1 & 0x80) && (val2 != 0x5c))) {
> +				goto error1;
> +			}
> +		}
> +		/* If Winbond chip, address of chip and W83791D_REG_I2C_ADDR
> +		   should match */
> +		if (w83791d_read(new_client, W83791D_REG_I2C_ADDR) != address) {
> +			dev_dbg(dev, "Detection failed at step 5\n");
> +			goto error1;
> +		}
> +	}

Please renumber the debug messages, and add one for the second possible
failure. I have a pending patch doing that for the w83792d driver:
http://www.kernel.org/pub/linux/kernel/people/gregkh/gregkh-2.6/patches/i2c/hwmon-w83792d-quiet-on-misdetection.patch

> +	/* A few vars need to be filled upon startup */
> +	for (i = 1; i <= NUMBER_OF_FANIN; i++) {
> +		data->fan_min[i - 1] = w83791d_read(new_client,
> +						W83791D_REG_FAN_MIN[i]);
> +	}

Isn't it a nice off-by-one I'm spotting here? Please fix!

+	for (i = 0; i < 2; i++) {
+		device_create_file(dev, &sda_beep_ctrl[i].dev_attr);
+	}

You could use ARRAY_SIZE(sda_beep_ctrl) instead of hardcoding 2. This
is easier to understand for the reader, and more robust to future
changes too. I'm not sure if this array really adds anything though.

> +static int w83791d_read(struct i2c_client *client, u8 reg)
> +{
> +	return i2c_smbus_read_byte_data(client, reg);
> +}
> +
> +static int w83791d_write(struct i2c_client *client, u8 reg, u8 value)
> +{
> +	return i2c_smbus_write_byte_data(client, reg, value);
> +}

Please inline these.
http://www.kernel.org/git/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=0d0ab7fe4c009c40dc485731f9ad98e1d336ddae

> +	temp2_cfg = w83791d_read(client, W83791D_REG_TEMP2_CONFIG);
> +	temp3_cfg = w83791d_read(client, W83791D_REG_TEMP3_CONFIG);
> +	w83791d_write(client, W83791D_REG_TEMP2_CONFIG, temp2_cfg & 0xe6);
> +	w83791d_write(client, W83791D_REG_TEMP3_CONFIG, temp3_cfg & 0xe6);

What are these doing? This needs a comment. Also note that you could do
with a single temporary variable (or even without one.)

> +	if (time_after(jiffies, data->last_updated + (HZ * 3))
> +			|| time_before(jiffies, data->last_updated)
> +			|| !data->valid) {

The time_before() check isn't needed. time_after() takes care of the
jiffies count wrapping on its own. Looks like the w83792d driver is
broken in that respect, a patch would be welcome. See the lm90 driver
for the proper way.

> +	dev_dbg(dev, "beep_enable is: 0x%08x\n", data->beep_enable);

0x%08x to display an u8?

> +	dev_dbg(dev, "vid is: 0x%04x\n", data->vid);
> +	dev_dbg(dev, "vrm is: 0x%04x\n", data->vrm);

These are u8 too.

> +}
> +
> +#endif

No blank line here please.

> +MODULE_AUTHOR("Charles Spirakis<bezaur at gmail.com>");

Add a space between your name and the address.

> +MODULE_DESCRIPTION("W83791D driver for linux-2.6");

The "for linux-2.6" is a bit redundant ;)

OK, that's it. Please fix the issues I mentioned (or explain why you
won't if you don't agree with my comments.) Then resubmit the driver,
and I should accept it and get it merged. Thanks for the good work!

Other things which need to be done about this driver:

* Userspace part. Does libsensors handle this driver properly already?

* Linux 2.4. For now the W83791D chip is handled by the w83781d driver.
This might be a bit confusing to the user that we have a dedicated
driver in 2.6 and not in 2.4. OTOH I don't have the time to split
support to a separate driver myself. If anyone wants to do it, this is
welcome, but probably not required.

* sensors-detect needs to be updated, as for now it points the W83791D
owners to the w83781d driver regardless of the kernel version. Patch
anyone?

* The website needs to be updated to mention the new driver. I'll take
care of that.

Thanks again,
-- 
Jean Delvare




More information about the lm-sensors mailing list