// SPDX-License-Identifier: GPL-2.0-or-later /* * smsc47m1.c - Part of lm_sensors, Linux kernel modules * for hardware monitoring * * Supports the SMSC LPC47B27x, LPC47M10x, LPC47M112, LPC47M13x, * LPC47M14x, LPC47M15x, LPC47M192, LPC47M292 and LPC47M997 * Super-I/O chips. * * Copyright (C) 2002 Mark D. Studebaker <mdsxyz123@yahoo.com> * Copyright (C) 2004-2007 Jean Delvare <jdelvare@suse.de> * Ported to Linux 2.6 by Gabriele Gorla <gorlik@yahoo.com> * and Jean Delvare
*/
static ssize_t fan_show(struct device *dev, struct device_attribute *devattr, char *buf)
{ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct smsc47m1_data *data = smsc47m1_update_device(dev, 0); int nr = attr->index; /* * This chip (stupidly) stops monitoring fan speed if PWM is * enabled and duty cycle is 0%. This is fine if the monitoring * and control concern the same fan, but troublesome if they are * not (which could as well happen).
*/ int rpm = (data->pwm[nr] & 0x7F) == 0x00 ? 0 :
FAN_FROM_REG(data->fan[nr],
DIV_FROM_REG(data->fan_div[nr]),
data->fan_preload[nr]); return sprintf(buf, "%d\n", rpm);
}
/* * Note: we save and restore the fan minimum here, because its value is * determined in part by the fan clock divider. This follows the principle * of least surprise; the user doesn't expect the fan minimum to change just * because the divider changed.
*/ static ssize_t fan_div_store(struct device *dev, struct device_attribute *devattr, constchar *buf, size_t count)
{ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct smsc47m1_data *data = dev_get_drvdata(dev); int nr = attr->index; long new_div; int err; long tmp;
u8 old_div = DIV_FROM_REG(data->fan_div[nr]);
err = kstrtol(buf, 10, &new_div); if (err) return err;
if (new_div == old_div) /* No change */ return count;
mutex_lock(&data->update_lock); switch (new_div) { case 1:
data->fan_div[nr] = 0; break; case 2:
data->fan_div[nr] = 1; break; case 4:
data->fan_div[nr] = 2; break; case 8:
data->fan_div[nr] = 3; break; default:
mutex_unlock(&data->update_lock); return -EINVAL;
}
val = force_id ? force_id : superio_inb(SUPERIO_REG_DEVID);
/* * SMSC LPC47M10x/LPC47M112/LPC47M13x (device id 0x59), LPC47M14x * (device id 0x5F) and LPC47B27x (device id 0x51) have fan control. * The LPC47M15x and LPC47M192 chips "with hardware monitoring block" * can do much more besides (device id 0x60). * The LPC47M997 is undocumented, but seems to be compatible with * the LPC47M192, and has the same device id. * The LPC47M292 (device id 0x6B) is somewhat compatible, but it * supports a 3rd fan, and the pin configuration registers are * unfortunately different. * The LPC47M233 has the same device id (0x6B) but is not compatible. * We check the high bit of the device revision register to * differentiate them.
*/ switch (val) { case 0x51:
pr_info("Found SMSC LPC47B27x\n");
sio_data->type = smsc47m1; break; case 0x59:
pr_info("Found SMSC LPC47M10x/LPC47M112/LPC47M13x\n");
sio_data->type = smsc47m1; break; case 0x5F:
pr_info("Found SMSC LPC47M14x\n");
sio_data->type = smsc47m1; break; case 0x60:
pr_info("Found SMSC LPC47M15x/LPC47M192/LPC47M997\n");
sio_data->type = smsc47m1; break; case 0x6B: if (superio_inb(SUPERIO_REG_DEVREV) & 0x80) {
pr_debug("Found SMSC LPC47M233, unsupported\n");
superio_exit(); return -ENODEV;
}
superio_select();
addr = (superio_inb(SUPERIO_REG_BASE) << 8)
| superio_inb(SUPERIO_REG_BASE + 1); if (addr == 0) {
pr_info("Device address not set, will not use\n");
superio_exit(); return -ENODEV;
}
/* * Enable only if address is set (needed at least on the * Compaq Presario S4000NX)
*/
sio_data->activate = superio_inb(SUPERIO_REG_ACT); if ((sio_data->activate & 0x01) == 0) {
pr_info("Enabling device\n");
superio_outb(SUPERIO_REG_ACT, sio_data->activate | 0x01);
}
superio_exit(); return addr;
}
/* Restore device to its initial state */ staticvoid smsc47m1_restore(conststruct smsc47m1_sio_data *sio_data)
{ if ((sio_data->activate & 0x01) == 0) { if (!superio_enter()) {
superio_select();
pr_info("Disabling device\n");
superio_outb(SUPERIO_REG_ACT, sio_data->activate);
superio_exit();
} else {
pr_warn("Failed to disable device\n");
}
}
}
#define CHECK 1 #define REQUEST 2
/* * This function can be used to: * - test for resource conflicts with ACPI * - request the resources * We only allocate the I/O ports we really need, to minimize the risk of * conflicts with ACPI or with other drivers.
*/ staticint __init smsc47m1_handle_resources(unsignedshort address, enum chips type, int action, struct device *dev)
{ staticconst u8 ports_m1[] = { /* register, region length */
0x04, 1,
0x33, 4,
0x56, 7,
};
/* * If no function is properly configured, there's no point in * actually registering the chip.
*/
pwm1 = (smsc47m1_read_value(data, SMSC47M1_REG_PPIN(0)) & 0x05)
== 0x04;
pwm2 = (smsc47m1_read_value(data, SMSC47M1_REG_PPIN(1)) & 0x05)
== 0x04; if (data->type == smsc47m2) {
fan1 = (smsc47m1_read_value(data, SMSC47M2_REG_TPIN1)
& 0x0d) == 0x09;
fan2 = (smsc47m1_read_value(data, SMSC47M2_REG_TPIN2)
& 0x0d) == 0x09;
fan3 = (smsc47m1_read_value(data, SMSC47M2_REG_TPIN3)
& 0x0d) == 0x0d;
pwm3 = (smsc47m1_read_value(data, SMSC47M2_REG_PPIN3)
& 0x0d) == 0x08;
} else {
fan1 = (smsc47m1_read_value(data, SMSC47M1_REG_TPIN(0))
& 0x05) == 0x05;
fan2 = (smsc47m1_read_value(data, SMSC47M1_REG_TPIN(1))
& 0x05) == 0x05;
fan3 = 0;
pwm3 = 0;
} if (!(fan1 || fan2 || fan3 || pwm1 || pwm2 || pwm3)) {
dev_warn(dev, "Device not configured, will not use\n"); return -ENODEV;
}
/* * Some values (fan min, clock dividers, pwm registers) may be * needed before any update is triggered, so we better read them * at least once here. We don't usually do it that way, but in * this particular case, manually reading 5 registers out of 8 * doesn't make much sense and we're better using the existing * function.
*/
smsc47m1_update_device(dev, 1);
/* Register sysfs hooks */ if (fan1) {
err = sysfs_create_group(&dev->kobj,
&smsc47m1_group_fan1); if (err) goto error_remove_files;
} else
dev_dbg(dev, "Fan 1 not enabled by hardware, skipping\n");
if (fan2) {
err = sysfs_create_group(&dev->kobj,
&smsc47m1_group_fan2); if (err) goto error_remove_files;
} else
dev_dbg(dev, "Fan 2 not enabled by hardware, skipping\n");
if (fan3) {
err = sysfs_create_group(&dev->kobj,
&smsc47m1_group_fan3); if (err) goto error_remove_files;
} elseif (data->type == smsc47m2)
dev_dbg(dev, "Fan 3 not enabled by hardware, skipping\n");
if (pwm1) {
err = sysfs_create_group(&dev->kobj,
&smsc47m1_group_pwm1); if (err) goto error_remove_files;
} else
dev_dbg(dev, "PWM 1 not enabled by hardware, skipping\n");
if (pwm2) {
err = sysfs_create_group(&dev->kobj,
&smsc47m1_group_pwm2); if (err) goto error_remove_files;
} else
dev_dbg(dev, "PWM 2 not enabled by hardware, skipping\n");
if (pwm3) {
err = sysfs_create_group(&dev->kobj,
&smsc47m1_group_pwm3); if (err) goto error_remove_files;
} elseif (data->type == smsc47m2)
dev_dbg(dev, "PWM 3 not enabled by hardware, skipping\n");
err = sysfs_create_group(&dev->kobj, &smsc47m1_group); if (err) goto error_remove_files;
/* * smsc47m1_remove() lives in .exit.text. For drivers registered via * module_platform_driver_probe() this ok because they cannot get unbound at * runtime. The driver needs to be marked with __refdata, otherwise modpost * triggers a section mismatch warning.
*/ staticstruct platform_driver smsc47m1_driver __refdata = {
.driver = {
.name = DRVNAME,
},
.remove = __exit_p(smsc47m1_remove),
};
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.