On Mon, 2006-06-26 at 22:39 +0200, Jean Delvare wrote: > Hi Brian, > > > I've been working on a driver for the chip, but I can't get it to > > interface with sensors. I have it reading from the registers correctly, > > and I used a printk statement to make sure that the driver was reading, > > and storing the values correctly, but when I run sensors it doesn't > > print out anything. I based the code off of your lm63 driver. The driver > > does initialize, and detect. Would you like to look at it, and how woule > > you like me to send it? I have a couple things to fix with temperature > > calculation, hyst, and min, and max, but I first want to get it to talk > > to userland software. > > If you want "sensors" to display anything for your chip, you must add > support for it to libsensors, and to sensors itself. You must also make > sure that you register your driver with the hwmon class. > > You are welcome to post your code on this list, as a -p1 patch against > either Linus' or Andrew's tree. You can post the patch either inline > (if your mailer can do this properly) or as an attchement (type > text/plain) so that we can comment on it and help you. > My patch. I've tested the driver on my own mainboard, and everything seems to work by what I read from the datasheet. --- /dev/null 2000-10-22 01:01:00.000000000 -0600 +++ linux-2.6.17-gentoo/drivers/hwmon/f75383.c 2006-06-26 17:52:23.000000000 -0600 @@ -0,0 +1,578 @@ +/* + * f75383.c - driver for the Fintek F75383/F75384 temperature sensor + * + * Copyright (C) 2004-2006 Jean Delvare <khali at linux-fr.org> + * Brian Beardall <brian at rapsure.net> + * Based on the lm90 and lm63 driver. + * + * The F75383 is a sensor chip made by Fintek. It measures + * two temperatures (its own and one external one). Complete datasheet can be + * obtained from Fintek's website at: + * http://www.fintek.com.tw/files/productfiles/F75383_384_V030P.pdf + * + * The F75383 is a temperature sensors with one remote, and one local sensor. + * the sensors behave the same. + * + * 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-sysfs.h> +#include <linux/hwmon.h> +#include <linux/err.h> +#include <linux/mutex.h> + +/* + * Addresses to scan + * Address is fully defined internally and cannot be changed. + */ + +static unsigned short normal_i2c[] = { 0x4c, I2C_CLIENT_END }; + +/* + * Insmod parameters + */ + +I2C_CLIENT_INSMOD_1(f75383); + +/* + * The F75383 registers + */ + + +#define F75383_REG_CONFIG_READ 0x03 +#define F75383_REG_CONFIG_WRITE 0x09 +#define F75383_STATUS 0x02 +#define F75383_CONV_RATE 0x08 +#define F75383_ONE_SHOT 0x0F +#define F75383_ALERT_TIMEOUT 0x22 +#define F75383_STATUS_ARA 0x24 + +#define F75383_CHIP_ID1 0x5A /* Read to know that the driver is for it */ +#define F75383_CHIP_ID2 0x5B + +#define F75383_VENDOR_ID1_LOC1 0x5D +#define F75383_VENDOR_ID1_LOC2 0x5E +#define F75383_VENDOR_ID2 0xFE /* might be different */ +/* #define F75383_VALUE_RAM 0x10 - 0x2F */ +#define F75383_VT1_HIGH 0x00 +#define F75383_VT1_LOW 0x1A +#define F75383_VT2_HIGH 0x01 +#define F75383_VT2_LOW 0x10 +#define F75383_VT1_HIGH_LIMIT_HIGH_READ 0x05 +#define F75383_VT1_HIGH_LIMIT_LOW_BYTE 0x1B +#define F75383_VT1_HIGH_LIMIT_HIGH_WRITE 0x0B +#define F75383_VT1_LOW_LIMIT_HIGH_READ 0x06 +#define F75383_VT1_LOW_LIMIT_LOW_BYTE 0x1C +#define F75383_VT1_LOW_LIMIT_HIGH_WRITE 0x0C +#define F75383_VT2_HIGH_LIMIT_HIGH_READ 0x07 +#define F75383_VT2_HIGH_LIMIT_LOW_BYTE 0x13 +#define F75383_VT2_HIGH_LIMIT_HIGH_WRITE 0x0D +#define F75383_VT2_LOW_LIMIT_HIGH_READ 0x08 +#define F75383_VT2_LOW_LIMIT_LOW_BYTE 0x14 +#define F75383_VT2_LOW_LIMIT_HIGH_WRITE 0x0E +#define F75383_VT1_THERM_LIMIT 0x20 +#define F75383_VT1_THERM_HYST 0x21 +#define F75383_VT2_THERM_LIMIT 0x19 +#define F75383_VT2_THERM_HYST 0x23 + + +/* For the F75383 Both temperature sensors are read the same. VT1 is local, + * and VT2 is external, and on ECS mainboards is + * connected to the CPU for the temperature. There is no FAN circutry, and there + * is only one method for calculating the temperatures. The High Byte is just a value + * from 0 - 144 degrees C. For each increase in the number there is a corresponding change in + * the temperature. The Low Byte uses the upper nibble for + *temperature changes of .125 degrees C. + */ + +#define TEMP_FROM_REG(reg) ((reg) / 32 * 125) +#define TEMP_TO_REG(val) ((val) <= 0 ? 0x0000 : \ + (val) >= 140875 ? 0x8CE0 : \ + (val) / 125 * 32) +#define HYST_FROM_REG(reg) ((reg) * 1000) +#define HYST_TO_REG(val) ((val) <= 0 ? 0 : \ + (val) >= 140000 ? 140 : \ + (val) / 1000) + +/* + * Functions declaration + */ + +static int f75383_attach_adapter(struct i2c_adapter *adapter); +static int f75383_detach_client(struct i2c_client *client); + +static struct f75383_data *f75383_update_device(struct device *dev); + +static int f75383_detect(struct i2c_adapter *adapter, int address, int kind); +static void f75383_init_client(struct i2c_client *client); + +/* + * Driver data (common to all clients) + */ + +static struct i2c_driver f75383_driver = { + .driver = { + .name = "F75383", + }, + .attach_adapter = f75383_attach_adapter, + .detach_client = f75383_detach_client, +}; + +/* + * Client data (each client gets its own) + */ + +struct f75383_data { + struct i2c_client client; + struct class_device *class_dev; + struct mutex update_lock; + char valid; /* zero until following fields are valid */ + unsigned long last_updated; /* in jiffies */ + + /* registers values */ + u8 config; + + u16 temp1[3]; /* 0: VT1 input + 1: VT1 low limit + 2: VT1 high limit */ + u16 temp2[3]; /* + 0: VT2 input + 1: VT2 low limit + 2: VT2 high limit */ + u8 temp1_crit_hyst; + u8 temp2_crit_hyst; + u8 temp1_crit; + u8 temp2_crit; + u8 alarms; +}; + +/* + * Sysfs callback functions and files + */ + + +static ssize_t show_temp1(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct f75383_data *data = f75383_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp1[attr->index])); +} + +static ssize_t show_temp2(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct f75383_data *data = f75383_update_device(dev); + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp2[attr->index])); +} + +static ssize_t set_temp1(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + static const u8 reg[4]= { + F75383_VT1_LOW_LIMIT_HIGH_WRITE, + F75383_VT1_LOW_LIMIT_LOW_BYTE, + F75383_VT1_HIGH_LIMIT_HIGH_WRITE, + F75383_VT1_HIGH_LIMIT_LOW_BYTE, + }; + + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct i2c_client *client = to_i2c_client(dev); + struct f75383_data *data = i2c_get_clientdata(client); + long val = simple_strtol(buf, NULL, 10); + int nr = attr->index; + + mutex_lock(&data->update_lock); + data->temp1[nr] = TEMP_TO_REG(val); + i2c_smbus_write_byte_data(client, reg[(nr - 1) * 2], + data->temp1[nr] >> 8); + i2c_smbus_write_byte_data(client, reg[(nr - 1) * 2 + 1], + data->temp1[nr] & 0xff); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t set_temp2(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + static const u8 reg[4]= { + F75383_VT2_LOW_LIMIT_HIGH_WRITE, + F75383_VT2_LOW_LIMIT_LOW_BYTE, + F75383_VT2_HIGH_LIMIT_HIGH_WRITE, + F75383_VT2_HIGH_LIMIT_LOW_BYTE, + }; + + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct i2c_client *client = to_i2c_client(dev); + struct f75383_data *data = i2c_get_clientdata(client); + long val = simple_strtol(buf, NULL, 10); + int nr = attr->index; + + mutex_lock(&data->update_lock); + data->temp2[nr] = TEMP_TO_REG(val); + i2c_smbus_write_byte_data(client, reg[(nr - 1) * 2], + data->temp2[nr] >> 8); + i2c_smbus_write_byte_data(client, reg[(nr - 1) * 2 + 1], + data->temp2[nr] & 0xff); + mutex_unlock(&data->update_lock); + return count; +} + +/* Show the THERM limit temperature. This is an eight bit value */ + +static ssize_t show_temp1_crit(struct device *dev, struct device_attribute *dummy, + char *buf) +{ + struct f75383_data *data = f75383_update_device(dev); + return sprintf(buf, "%d\n", HYST_FROM_REG(data->temp1_crit)); +} + +static ssize_t show_temp2_crit(struct device *dev, struct device_attribute *dummy, + char *buf) +{ + struct f75383_data *data = f75383_update_device(dev); + return sprintf(buf, "%d\n", HYST_FROM_REG(data->temp2_crit)); +} + + +/* Set the THERM limit, I named the functions with crit because of the function + * in libsensors + */ +static ssize_t set_temp1_crit(struct device *dev, struct device_attribute *dummy, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct f75383_data *data = i2c_get_clientdata(client); + long crit = simple_strtol(buf, NULL, 10); + + mutex_lock(&data->update_lock); + i2c_smbus_write_byte_data(client, F75383_VT1_THERM_LIMIT, + HYST_TO_REG(crit)); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t set_temp2_crit(struct device *dev, struct device_attribute *dummy, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct f75383_data *data = i2c_get_clientdata(client); + long crit = simple_strtol(buf, NULL, 10); + + mutex_lock(&data->update_lock); + i2c_smbus_write_byte_data(client, F75383_VT2_THERM_LIMIT, + HYST_TO_REG(crit)); + mutex_unlock(&data->update_lock); + return count; +} + +/* Hysteresis register holds a relative value, while we want to present + an absolute to user-space */ +static ssize_t show_temp1_crit_hyst(struct device *dev, struct device_attribute *dummy, + char *buf) +{ + struct f75383_data *data = f75383_update_device(dev); + return sprintf(buf, "%d\n", HYST_FROM_REG(data->temp1_crit_hyst)); +} + +static ssize_t show_temp2_crit_hyst(struct device *dev, struct device_attribute *dummy, + char *buf) +{ + struct f75383_data *data = f75383_update_device(dev); + return sprintf(buf, "%d\n", HYST_FROM_REG(data->temp2_crit_hyst)); +} + +/* And now the other way around, user-space provides an absolute + hysteresis value and we have to store a relative one */ +static ssize_t set_temp1_crit_hyst(struct device *dev, struct device_attribute *dummy, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct f75383_data *data = i2c_get_clientdata(client); + long hyst = simple_strtol(buf, NULL, 10); + + mutex_lock(&data->update_lock); + i2c_smbus_write_byte_data(client, F75383_VT1_THERM_HYST, + HYST_TO_REG(hyst)); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t set_temp2_crit_hyst(struct device *dev, struct device_attribute *dummy, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct f75383_data *data = i2c_get_clientdata(client); + long hyst = simple_strtol(buf, NULL, 10); + + mutex_lock(&data->update_lock); + i2c_smbus_write_byte_data(client, F75383_VT2_THERM_HYST, + HYST_TO_REG(hyst)); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_alarms(struct device *dev, struct device_attribute *dummy, + char *buf) +{ + struct f75383_data *data = f75383_update_device(dev); + return sprintf(buf, "%u\n", data->alarms); +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp1, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_temp1, + set_temp1, 1); +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp1, + set_temp1, 2); +static DEVICE_ATTR(temp1_crit, S_IWUSR | S_IRUGO, show_temp1_crit, + set_temp1_crit); +static DEVICE_ATTR(temp1_crit_hyst, S_IWUSR | S_IRUGO, show_temp1_crit_hyst, + set_temp1_crit_hyst); + +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp2, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_min, S_IWUSR | S_IRUGO, show_temp2, + set_temp2, 1); +static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_temp2, + set_temp2, 2); +static DEVICE_ATTR(temp2_crit, S_IWUSR | S_IRUGO, show_temp2_crit, + set_temp2_crit); +static DEVICE_ATTR(temp2_crit_hyst, S_IWUSR | S_IRUGO, show_temp2_crit_hyst, + set_temp2_crit_hyst); + +static DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL); + +/* + * Real code + */ + +static int f75383_attach_adapter(struct i2c_adapter *adapter) +{ + if (!(adapter->class & I2C_CLASS_HWMON)) + return 0; + return i2c_probe(adapter, &addr_data, f75383_detect); +} + +/* + * The following function does more than just detection. If detection + * succeeds, it also registers the new chip. + */ +static int f75383_detect(struct i2c_adapter *adapter, int address, int kind) +{ + struct i2c_client *new_client; + struct f75383_data *data; + int err = 0; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + goto exit; + + if (!(data = kzalloc(sizeof(struct f75383_data), GFP_KERNEL))) { + err = -ENOMEM; + goto exit; + } + + /* The common I2C client data is placed right before the + F75383-specific data. */ + new_client = &data->client; + i2c_set_clientdata(new_client, data); + new_client->addr = address; + new_client->adapter = adapter; + new_client->driver = &f75383_driver; + new_client->flags = 0; + + /* Default to an F75383 if forced */ + if (kind == 0) + kind = f75383; + + if (kind < 0) { /* must identify */ + u8 chip_id1, chip_id2, vendor_id1_loc1, vendor_id1_loc2; + + chip_id1 = i2c_smbus_read_byte_data(new_client, + F75383_CHIP_ID1); + chip_id2 = i2c_smbus_read_byte_data(new_client, + F75383_CHIP_ID2); + vendor_id1_loc1 = i2c_smbus_read_byte_data(new_client, + F75383_VENDOR_ID1_LOC1); + vendor_id1_loc2 = i2c_smbus_read_byte_data(new_client, + F75383_VENDOR_ID1_LOC2); + + if (chip_id1 == 0x03 /* */ + && chip_id2 == 0x03 /* */ + && vendor_id1_loc1 == 0x19 + && vendor_id1_loc2 == 0x34 ) { + kind = f75383; + } else { /* failed */ + dev_dbg(&adapter->dev, "Unsupported chip " + "(chip_id1=0x%02X, chip_id2=0x%02X).\n", + chip_id1, chip_id2); + goto exit_free; + } + } + + strlcpy(new_client->name, "f75383", I2C_NAME_SIZE); + data->valid = 0; + mutex_init(&data->update_lock); + + /* Tell the I2C layer a new client has arrived */ + if ((err = i2c_attach_client(new_client))) + goto exit_free; + + /* Initialize the F75383 chip */ + f75383_init_client(new_client); + + /* Register sysfs hooks */ + data->class_dev = hwmon_device_register(&new_client->dev); + if (IS_ERR(data->class_dev)) { + err = PTR_ERR(data->class_dev); + goto exit_detach; + } + + device_create_file(&new_client->dev, + &sensor_dev_attr_temp1_input.dev_attr); + device_create_file(&new_client->dev, + &sensor_dev_attr_temp2_input.dev_attr); + device_create_file(&new_client->dev, + &sensor_dev_attr_temp1_min.dev_attr); + device_create_file(&new_client->dev, + &sensor_dev_attr_temp2_min.dev_attr); + device_create_file(&new_client->dev, + &sensor_dev_attr_temp1_max.dev_attr); + device_create_file(&new_client->dev, + &sensor_dev_attr_temp2_max.dev_attr); + device_create_file(&new_client->dev, &dev_attr_temp1_crit); + device_create_file(&new_client->dev, &dev_attr_temp2_crit); + device_create_file(&new_client->dev, &dev_attr_temp1_crit_hyst); + device_create_file(&new_client->dev, &dev_attr_temp2_crit_hyst); + device_create_file(&new_client->dev, &dev_attr_alarms); + + return 0; + +exit_detach: + i2c_detach_client(new_client); +exit_free: + kfree(data); +exit: + return err; +} + +/* Idealy we shouldn't have to initialize anything, since the BIOS + should have taken care of everything */ +static void f75383_init_client(struct i2c_client *client) +{ + struct f75383_data *data = i2c_get_clientdata(client); + + data->config = i2c_smbus_read_byte_data(client, F75383_REG_CONFIG_READ); + + /* Start converting if needed */ + if (data->config & 0x40) { /* standby */ + dev_dbg(&client->dev, "Switching to operational mode"); + data->config &= 0xBF; + i2c_smbus_write_byte_data(client, F75383_REG_CONFIG_WRITE, + data->config); + } + +} + +static int f75383_detach_client(struct i2c_client *client) +{ + struct f75383_data *data = i2c_get_clientdata(client); + int err; + + hwmon_device_unregister(data->class_dev); + + if ((err = i2c_detach_client(client))) + return err; + + kfree(data); + return 0; +} + +static struct f75383_data *f75383_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct f75383_data *data = i2c_get_clientdata(client); + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { + + /* order matters for temp2_input */ // Modify this + data->temp1[0] = i2c_smbus_read_byte_data(client, + F75383_VT1_HIGH) << 8; + data->temp1[0] |= i2c_smbus_read_byte_data(client, + F75383_VT1_LOW); + data->temp1[1] = (i2c_smbus_read_byte_data(client, + F75383_VT1_LOW_LIMIT_HIGH_READ) << 8) + | i2c_smbus_read_byte_data(client, + F75383_VT1_LOW_LIMIT_LOW_BYTE); + data->temp1[2] = (i2c_smbus_read_byte_data(client, + F75383_VT1_HIGH_LIMIT_HIGH_READ) << 8) + | i2c_smbus_read_byte_data(client, + F75383_VT1_HIGH_LIMIT_LOW_BYTE); + data->temp1_crit_hyst = i2c_smbus_read_byte_data(client, + F75383_VT1_THERM_HYST); + data->temp1_crit = i2c_smbus_read_byte_data(client, + F75383_VT1_THERM_LIMIT); + + data->temp2[0] = i2c_smbus_read_byte_data(client, + F75383_VT2_HIGH) << 8; + data->temp2[0] |= i2c_smbus_read_byte_data(client, + F75383_VT2_LOW); + data->temp2[1] = (i2c_smbus_read_byte_data(client, + F75383_VT2_LOW_LIMIT_HIGH_READ) << 8) + | i2c_smbus_read_byte_data(client, + F75383_VT2_LOW_LIMIT_LOW_BYTE); + data->temp2[2] = (i2c_smbus_read_byte_data(client, + F75383_VT2_HIGH_LIMIT_HIGH_READ) << 8) + | i2c_smbus_read_byte_data(client, + F75383_VT2_HIGH_LIMIT_LOW_BYTE); + data->temp2_crit_hyst = i2c_smbus_read_byte_data(client, + F75383_VT2_THERM_HYST); + data->temp2_crit = i2c_smbus_read_byte_data(client, + F75383_VT2_THERM_LIMIT); + + data->alarms = i2c_smbus_read_byte_data(client, + F75383_STATUS) & 0x7F; + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +static int __init sensors_f75383_init(void) +{ + return i2c_add_driver(&f75383_driver); +} + +static void __exit sensors_f75383_exit(void) +{ + i2c_del_driver(&f75383_driver); +} + +MODULE_AUTHOR("Jean Delvare <khali at linux-fr.org> Brian Beardall <brian at rapsure.net>"); +MODULE_DESCRIPTION("F75383 driver"); +MODULE_LICENSE("GPL"); + +module_init(sensors_f75383_init); +module_exit(sensors_f75383_exit); --- linux-2.6.17/drivers/hwmon/Kconfig 2006-06-17 19:49:35.000000000 -0600 +++ linux-2.6.17-gentoo/drivers/hwmon/Kconfig 2006-06-26 17:57:20.000000000 -0600 @@ -450,6 +450,17 @@ config SENSORS_HDAPS Say Y here if you have an applicable laptop and want to experience the awesome power of hdaps. +config SENSORS_F75383 + tristate "F75383" + depends on HWMON && I2C && EXPERIMENTAL + help + If you say yes here you get support for the Fintek F75383 + digital temperature sensor control. Such chips are found + on the ECS RX480-A, and ECS NFORCE3-A939 motherboard, among others. + + This driver can also be built as a module. If so, the module + will be called f75383. + config HWMON_DEBUG_CHIP bool "Hardware Monitoring Chip debugging messages" depends on HWMON --- linux-2.6.17/drivers/hwmon/Makefile 2006-06-17 19:49:35.000000000 -0600 +++ linux-2.6.17-gentoo/drivers/hwmon/Makefile 2006-06-24 18:57:04.000000000 -0600 @@ -44,6 +44,7 @@ obj-$(CONFIG_SENSORS_VIA686A) += via686a obj-$(CONFIG_SENSORS_VT8231) += vt8231.o obj-$(CONFIG_SENSORS_W83627EHF) += w83627ehf.o obj-$(CONFIG_SENSORS_W83L785TS) += w83l785ts.o +obj-$(CONFIG_SENSORS_F75383) += f75383.o ifeq ($(CONFIG_HWMON_DEBUG_CHIP),y) EXTRA_CFLAGS += -DDEBUG