/* * minimum pwm at which the fan is driven (pwm can be increased depending on * the temp. Notice that for the scy some fans share there minimum speed. * Also notice that with the scy the sensor order is different than with the * other chips, this order was in the 2.4 driver and kept for consistency.
*/ staticconst u8 FSCHMD_REG_FAN_MIN[7][7] = {
{ 0x55, 0x65 }, /* pos */
{ 0x55, 0x65, 0xb5 }, /* her */
{ 0x65, 0x65, 0x55, 0xa5, 0x55, 0xa5 }, /* scy */
{ 0x55, 0x65, 0xa5, 0xb5 }, /* hrc */
{ 0x55, 0x65, 0xa5, 0xb5, 0xc5 }, /* hmd */
{ 0x55, 0x65, 0xa5, 0xb5, 0xc5 }, /* hds */
{ 0x54, 0x64, 0x74, 0x84, 0x94, 0xa4, 0xb4 }, /* syl */
};
/* * temperature high limit registers, FSC does not document these. Proven to be * there with field testing on the fscher and fschrc, already supported / used * in the fscscy 2.4 driver. FSC has confirmed that the fschmd has registers * at these addresses, but doesn't want to confirm they are the same as with * the fscher??
*/ staticconst u8 FSCHMD_REG_TEMP_LIMIT[7][11] = {
{ 0, 0, 0 }, /* pos */
{ 0x76, 0x86, 0x96 }, /* her */
{ 0x76, 0xd6, 0x86, 0x96 }, /* scy */
{ 0x76, 0x86, 0x96 }, /* hrc */
{ 0x76, 0x86, 0x96, 0xd6, 0xe6 }, /* hmd */
{ 0x76, 0x86, 0x96, 0xd6, 0xe6 }, /* hds */
{ 0x5a, 0x6a, 0x7a, 0x8a, 0x9a, 0xaa, /* syl */
0xba, 0xca, 0xda, 0xea, 0xfa },
};
/* * These were found through experimenting with an fscher, currently they are * not used, but we keep them around for future reference. * On the fscsyl AUTOP1 lives at 0x#c (so 0x5c for fan1, 0x6c for fan2, etc), * AUTOP2 lives at 0x#e, and 0x#1 is a bitmask defining which temps influence * the fan speed. * static const u8 FSCHER_REG_TEMP_AUTOP1[] = { 0x73, 0x83, 0x93 }; * static const u8 FSCHER_REG_TEMP_AUTOP2[] = { 0x75, 0x85, 0x95 };
*/
/* temp status register bitmasks */ #define FSCHMD_TEMP_WORKING 0x01 #define FSCHMD_TEMP_ALERT 0x02 #define FSCHMD_TEMP_DISABLED 0x80 /* there only really is an alarm if the sensor is working and alert == 1 */ #define FSCHMD_TEMP_ALARM_MASK \
(FSCHMD_TEMP_WORKING | FSCHMD_TEMP_ALERT)
struct fschmd_data { struct i2c_client *client; struct device *hwmon_dev; struct mutex update_lock; struct mutex watchdog_lock; struct list_head list; /* member of the watchdog_data_list */ struct kref kref; struct miscdevice watchdog_miscdev; enum chips kind; unsignedlong watchdog_is_open; char watchdog_expect_close; char watchdog_name[10]; /* must be unique to avoid sysfs conflict */ bool valid; /* false until following fields are valid */ unsignedlong last_updated; /* in jiffies */
/* register values */
u8 revision; /* chip revision */
u8 global_control; /* global control register */
u8 watchdog_control; /* watchdog control register */
u8 watchdog_state; /* watchdog status register */
u8 watchdog_preset; /* watchdog counter preset on trigger val */
u8 volt[6]; /* voltage */
u8 temp_act[11]; /* temperature */
u8 temp_status[11]; /* status of sensor */
u8 temp_max[11]; /* high temp limit, notice: undocumented! */
u8 fan_act[7]; /* fans revolutions per second */
u8 fan_status[7]; /* fan status */
u8 fan_min[7]; /* fan min value for rps */
u8 fan_ripple[7]; /* divider for rps */
};
/* * Global variables to hold information read from special DMI tables, which are * available on FSC machines with an fscher or later chip. There is no need to * protect these with a lock as they are only modified from our attach function * which always gets called with the i2c-core lock held and never accessed * before the attach function is done with them.
*/ staticint dmi_mult[6] = { 490, 200, 100, 100, 200, 100 }; staticint dmi_offset[6] = { 0, 0, 0, 0, 0, 0 }; staticint dmi_vref = -1;
/* * Somewhat ugly :( global data pointer list with all fschmd devices, so that * we can find our device data as when using misc_register there is no other * method to get to ones device data from the open fop.
*/ static LIST_HEAD(watchdog_data_list); /* Note this lock not only protect list access, but also data.kref access */ static DEFINE_MUTEX(watchdog_data_mutex);
/* * Release our data struct when we're detached from the i2c client *and* all * references to our watchdog device are released
*/ staticvoid fschmd_release_resources(struct kref *ref)
{ struct fschmd_data *data = container_of(ref, struct fschmd_data, kref);
kfree(data);
}
static ssize_t temp_fault_show(struct device *dev, struct device_attribute *devattr, char *buf)
{ int index = to_sensor_dev_attr(devattr)->index; struct fschmd_data *data = fschmd_update_device(dev);
/* bit 0 set means sensor working ok, so no fault! */ if (data->temp_status[index] & FSCHMD_TEMP_WORKING) return sprintf(buf, "0\n"); else return sprintf(buf, "1\n");
}
static ssize_t temp_alarm_show(struct device *dev, struct device_attribute *devattr, char *buf)
{ int index = to_sensor_dev_attr(devattr)->index; struct fschmd_data *data = fschmd_update_device(dev);
switch (v) { case 2:
v = 1; break; case 4:
v = 2; break; case 8:
v = 3; break; default:
dev_err(dev, "fan_div value %lu not supported. Choose one of 2, 4 or 8!\n",
v); return -EINVAL;
}
static ssize_t pwm_auto_point1_pwm_show(struct device *dev, struct device_attribute *devattr, char *buf)
{ int index = to_sensor_dev_attr(devattr)->index; struct fschmd_data *data = fschmd_update_device(dev); int val = data->fan_min[index];
/* 0 = allow turning off (except on the syl), 1-255 = 50-100% */ if (val || data->kind == fscsyl)
val = val / 2 + 128;
return sprintf(buf, "%d\n", val);
}
static ssize_t pwm_auto_point1_pwm_store(struct device *dev, struct device_attribute *devattr, constchar *buf, size_t count)
{ int index = to_sensor_dev_attr(devattr)->index; struct fschmd_data *data = dev_get_drvdata(dev); unsignedlong v; int err;
err = kstrtoul(buf, 10, &v); if (err) return err;
/* reg: 0 = allow turning off (except on the syl), 1-255 = 50-100% */ if (v || data->kind == fscsyl) {
v = clamp_val(v, 128, 255);
v = (v - 128) * 2 + 1;
}
/* * The FSC hwmon family has the ability to force an attached alert led to flash * from software, we export this as an alert_led sysfs attr
*/ static ssize_t alert_led_show(struct device *dev, struct device_attribute *devattr, char *buf)
{ struct fschmd_data *data = fschmd_update_device(dev);
/* Write new timeout value */
i2c_smbus_write_byte_data(data->client,
FSCHMD_REG_WDOG_PRESET[data->kind], data->watchdog_preset); /* Write new control register, do not trigger! */
i2c_smbus_write_byte_data(data->client,
FSCHMD_REG_WDOG_CONTROL[data->kind],
data->watchdog_control & ~FSCHMD_WDOG_CONTROL_TRIGGER);
staticint watchdog_stop(struct fschmd_data *data)
{ int ret = 0;
mutex_lock(&data->watchdog_lock); if (!data->client) {
ret = -ENODEV; goto leave;
}
data->watchdog_control &= ~FSCHMD_WDOG_CONTROL_STARTED; /* * Don't store the stop flag in our watchdog control register copy, as * its a write only bit (read always returns 0)
*/
i2c_smbus_write_byte_data(data->client,
FSCHMD_REG_WDOG_CONTROL[data->kind],
data->watchdog_control | FSCHMD_WDOG_CONTROL_STOP);
leave:
mutex_unlock(&data->watchdog_lock); return ret;
}
/* * We get called from drivers/char/misc.c with misc_mtx hold, and we * call misc_register() from fschmd_probe() with watchdog_data_mutex * hold, as misc_register() takes the misc_mtx lock, this is a possible * deadlock, so we use mutex_trylock here.
*/ if (!mutex_trylock(&watchdog_data_mutex)) return -ERESTARTSYS;
list_for_each_entry(pos, &watchdog_data_list, list) { if (pos->watchdog_miscdev.minor == iminor(inode)) {
data = pos; break;
}
} /* Note we can never not have found data, so we don't check for this */
watchdog_is_open = test_and_set_bit(0, &data->watchdog_is_open); if (!watchdog_is_open)
kref_get(&data->kref);
mutex_unlock(&watchdog_data_mutex);
if (watchdog_is_open) return -EBUSY;
/* Start the watchdog */
watchdog_trigger(data);
filp->private_data = data;
switch (cmd) { case WDIOC_GETSUPPORT:
ident.firmware_version = data->revision; if (!nowayout)
ident.options |= WDIOF_MAGICCLOSE; if (copy_to_user((void __user *)arg, &ident, sizeof(ident)))
ret = -EFAULT; break;
case WDIOC_GETSTATUS:
ret = put_user(0, (int __user *)arg); break;
case WDIOC_GETBOOTSTATUS: if (data->watchdog_state & FSCHMD_WDOG_STATE_CARDRESET)
ret = put_user(WDIOF_CARDRESET, (int __user *)arg); else
ret = put_user(0, (int __user *)arg); break;
case WDIOC_KEEPALIVE:
ret = watchdog_trigger(data); break;
case WDIOC_GETTIMEOUT:
i = watchdog_get_timeout(data);
ret = put_user(i, (int __user *)arg); break;
case WDIOC_SETTIMEOUT: if (get_user(i, (int __user *)arg)) {
ret = -EFAULT; break;
}
ret = watchdog_set_timeout(data, i); if (ret > 0)
ret = put_user(ret, (int __user *)arg); break;
case WDIOC_SETOPTIONS: if (get_user(i, (int __user *)arg)) {
ret = -EFAULT; break;
}
if (i & WDIOS_DISABLECARD)
ret = watchdog_stop(data); elseif (i & WDIOS_ENABLECARD)
ret = watchdog_trigger(data); else
ret = -EINVAL;
/* * Detect, register, unregister and update device functions
*/
/* * DMI decode routine to read voltage scaling factors from special DMI tables, * which are available on FSC machines with an fscher or later chip.
*/ staticvoid fschmd_dmi_decode(conststruct dmi_header *header, void *dummy)
{ int i, mult[3] = { 0 }, offset[3] = { 0 }, vref = 0, found = 0;
/* * dmi code ugliness, we get passed the address of the contents of * a complete DMI record, but in the form of a dmi_header pointer, in * reality this address holds header->length bytes of which the header * are the first 4 bytes
*/
u8 *dmi_data = (u8 *)header;
/* We are looking for OEM-specific type 185 */ if (header->type != 185) return;
/* * we are looking for what Siemens calls "subtype" 19, the subtype * is stored in byte 5 of the dmi block
*/ if (header->length < 5 || dmi_data[4] != 19) return;
/* * After the subtype comes 1 unknown byte and then blocks of 5 bytes, * consisting of what Siemens calls an "Entity" number, followed by * 2 16-bit words in LSB first order
*/ for (i = 6; (i + 4) < header->length; i += 5) { /* entity 1 - 3: voltage multiplier and offset */ if (dmi_data[i] >= 1 && dmi_data[i] <= 3) { /* Our in sensors order and the DMI order differ */ constint shuffle[3] = { 1, 0, 2 }; int in = shuffle[dmi_data[i] - 1];
/* Check for twice the same entity */ if (found & (1 << in)) return;
/* entity 7: reference voltage */ if (dmi_data[i] == 7) { /* Check for twice the same entity */ if (found & 0x08) return;
vref = dmi_data[i + 1] | (dmi_data[i + 2] << 8);
found |= 0x08;
}
}
if (found == 0x0F) { for (i = 0; i < 3; i++) {
dmi_mult[i] = mult[i] * 10;
dmi_offset[i] = offset[i] * 10;
} /* * According to the docs there should be separate dmi entries * for the mult's and offsets of in3-5 of the syl, but on * my test machine these are not present
*/
dmi_mult[3] = dmi_mult[2];
dmi_mult[4] = dmi_mult[1];
dmi_mult[5] = dmi_mult[2];
dmi_offset[3] = dmi_offset[2];
dmi_offset[4] = dmi_offset[1];
dmi_offset[5] = dmi_offset[2];
dmi_vref = vref;
}
}
data = kzalloc(sizeof(struct fschmd_data), GFP_KERNEL); if (!data) return -ENOMEM;
i2c_set_clientdata(client, data);
mutex_init(&data->update_lock);
mutex_init(&data->watchdog_lock);
INIT_LIST_HEAD(&data->list);
kref_init(&data->kref); /* * Store client pointer in our data struct for watchdog usage * (where the client is found through a data ptr instead of the * otherway around)
*/
data->client = client;
data->kind = kind;
if (kind == fscpos) { /* * The Poseidon has hardwired temp limits, fill these * in for the alarm resetting code
*/
data->temp_max[0] = 70 + 128;
data->temp_max[1] = 50 + 128;
data->temp_max[2] = 50 + 128;
}
/* Read the special DMI table for fscher and newer chips */ if ((kind == fscher || kind >= fschrc) && dmi_vref == -1) {
dmi_walk(fschmd_dmi_decode, NULL); if (dmi_vref == -1) {
dev_warn(&client->dev, "Couldn't get voltage scaling factors from " "BIOS DMI table, using builtin defaults\n");
dmi_vref = 33;
}
}
/* Read in some never changing registers */
data->revision = i2c_smbus_read_byte_data(client, FSCHMD_REG_REVISION);
data->global_control = i2c_smbus_read_byte_data(client,
FSCHMD_REG_CONTROL);
data->watchdog_control = i2c_smbus_read_byte_data(client,
FSCHMD_REG_WDOG_CONTROL[data->kind]);
data->watchdog_state = i2c_smbus_read_byte_data(client,
FSCHMD_REG_WDOG_STATE[data->kind]);
data->watchdog_preset = i2c_smbus_read_byte_data(client,
FSCHMD_REG_WDOG_PRESET[data->kind]);
err = device_create_file(&client->dev, &dev_attr_alert_led); if (err) goto exit_detach;
for (i = 0; i < FSCHMD_NO_VOLT_SENSORS[data->kind]; i++) {
err = device_create_file(&client->dev,
&fschmd_attr[i].dev_attr); if (err) goto exit_detach;
}
for (i = 0; i < (FSCHMD_NO_TEMP_SENSORS[data->kind] * 4); i++) { /* Poseidon doesn't have TEMP_LIMIT registers */ if (kind == fscpos && fschmd_temp_attr[i].dev_attr.show ==
temp_max_show) continue;
if (kind == fscsyl) { if (i % 4 == 0)
data->temp_status[i / 4] =
i2c_smbus_read_byte_data(client,
FSCHMD_REG_TEMP_STATE
[data->kind][i / 4]); if (data->temp_status[i / 4] & FSCHMD_TEMP_DISABLED) continue;
}
err = device_create_file(&client->dev,
&fschmd_temp_attr[i].dev_attr); if (err) goto exit_detach;
}
for (i = 0; i < (FSCHMD_NO_FAN_SENSORS[data->kind] * 5); i++) { /* Poseidon doesn't have a FAN_MIN register for its 3rd fan */ if (kind == fscpos &&
!strcmp(fschmd_fan_attr[i].dev_attr.attr.name, "pwm3_auto_point1_pwm")) continue;
if (kind == fscsyl) { if (i % 5 == 0)
data->fan_status[i / 5] =
i2c_smbus_read_byte_data(client,
FSCHMD_REG_FAN_STATE
[data->kind][i / 5]); if (data->fan_status[i / 5] & FSCHMD_FAN_DISABLED) continue;
}
err = device_create_file(&client->dev,
&fschmd_fan_attr[i].dev_attr); if (err) goto exit_detach;
}
/* * We take the data_mutex lock early so that watchdog_open() cannot * run when misc_register() has completed, but we've not yet added * our data to the watchdog_data_list (and set the default timeout)
*/
mutex_lock(&watchdog_data_mutex); for (i = 0; i < ARRAY_SIZE(watchdog_minors); i++) { /* Register our watchdog part */
snprintf(data->watchdog_name, sizeof(data->watchdog_name), "watchdog%c", (i == 0) ? '\0' : ('0' + i));
data->watchdog_miscdev.name = data->watchdog_name;
data->watchdog_miscdev.fops = &watchdog_fops;
data->watchdog_miscdev.minor = watchdog_minors[i];
err = misc_register(&data->watchdog_miscdev); if (err == -EBUSY) continue; if (err) {
data->watchdog_miscdev.minor = 0;
dev_err(&client->dev, "Registering watchdog chardev: %d\n", err); break;
}
list_add(&data->list, &watchdog_data_list);
watchdog_set_timeout(data, 60);
dev_info(&client->dev, "Registered watchdog chardev major 10, minor: %d\n",
watchdog_minors[i]); break;
} if (i == ARRAY_SIZE(watchdog_minors)) {
data->watchdog_miscdev.minor = 0;
dev_warn(&client->dev, "Couldn't register watchdog chardev (due to no free minor)\n");
}
mutex_unlock(&watchdog_data_mutex);
/* Unregister the watchdog (if registered) */ if (data->watchdog_miscdev.minor) {
misc_deregister(&data->watchdog_miscdev); if (data->watchdog_is_open) {
dev_warn(&client->dev, "i2c client detached with watchdog open! " "Stopping watchdog.\n");
watchdog_stop(data);
}
mutex_lock(&watchdog_data_mutex);
list_del(&data->list);
mutex_unlock(&watchdog_data_mutex); /* Tell the watchdog code the client is gone */
mutex_lock(&data->watchdog_lock);
data->client = NULL;
mutex_unlock(&data->watchdog_lock);
}
/* * Check if registered in case we're called from fschmd_detect * to cleanup after an error
*/ if (data->hwmon_dev)
hwmon_device_unregister(data->hwmon_dev);
device_remove_file(&client->dev, &dev_attr_alert_led); for (i = 0; i < (FSCHMD_NO_VOLT_SENSORS[data->kind]); i++)
device_remove_file(&client->dev, &fschmd_attr[i].dev_attr); for (i = 0; i < (FSCHMD_NO_TEMP_SENSORS[data->kind] * 4); i++)
device_remove_file(&client->dev,
&fschmd_temp_attr[i].dev_attr); for (i = 0; i < (FSCHMD_NO_FAN_SENSORS[data->kind] * 5); i++)
device_remove_file(&client->dev,
&fschmd_fan_attr[i].dev_attr);
if (time_after(jiffies, data->last_updated + 2 * HZ) || !data->valid) {
for (i = 0; i < FSCHMD_NO_TEMP_SENSORS[data->kind]; i++) {
data->temp_act[i] = i2c_smbus_read_byte_data(client,
FSCHMD_REG_TEMP_ACT[data->kind][i]);
data->temp_status[i] = i2c_smbus_read_byte_data(client,
FSCHMD_REG_TEMP_STATE[data->kind][i]);
/* The fscpos doesn't have TEMP_LIMIT registers */ if (FSCHMD_REG_TEMP_LIMIT[data->kind][i])
data->temp_max[i] = i2c_smbus_read_byte_data(
client,
FSCHMD_REG_TEMP_LIMIT[data->kind][i]);
/* * reset alarm if the alarm condition is gone, * the chip doesn't do this itself
*/ if ((data->temp_status[i] & FSCHMD_TEMP_ALARM_MASK) ==
FSCHMD_TEMP_ALARM_MASK &&
data->temp_act[i] < data->temp_max[i])
i2c_smbus_write_byte_data(client,
FSCHMD_REG_TEMP_STATE[data->kind][i],
data->temp_status[i]);
}
for (i = 0; i < FSCHMD_NO_FAN_SENSORS[data->kind]; i++) {
data->fan_act[i] = i2c_smbus_read_byte_data(client,
FSCHMD_REG_FAN_ACT[data->kind][i]);
data->fan_status[i] = i2c_smbus_read_byte_data(client,
FSCHMD_REG_FAN_STATE[data->kind][i]);
data->fan_ripple[i] = i2c_smbus_read_byte_data(client,
FSCHMD_REG_FAN_RIPPLE[data->kind][i]);
/* The fscpos third fan doesn't have a fan_min */ if (FSCHMD_REG_FAN_MIN[data->kind][i])
data->fan_min[i] = i2c_smbus_read_byte_data(
client,
FSCHMD_REG_FAN_MIN[data->kind][i]);
/* reset fan status if speed is back to > 0 */ if ((data->fan_status[i] & FSCHMD_FAN_ALARM) &&
data->fan_act[i])
i2c_smbus_write_byte_data(client,
FSCHMD_REG_FAN_STATE[data->kind][i],
data->fan_status[i]);
}
for (i = 0; i < FSCHMD_NO_VOLT_SENSORS[data->kind]; i++)
data->volt[i] = i2c_smbus_read_byte_data(client,
FSCHMD_REG_VOLT[data->kind][i]);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.