// SPDX-License-Identifier: GPL-2.0-or-later /* * pc87360.c - Part of lm_sensors, Linux kernel modules * for hardware monitoring * Copyright (C) 2004, 2007 Jean Delvare <jdelvare@suse.de> * * Copied from smsc47m1.c: * Copyright (C) 2002 Mark D. Studebaker <mdsxyz123@yahoo.com> * * Supports the following chips: * * Chip #vin #fan #pwm #temp devid * PC87360 - 2 2 - 0xE1 * PC87363 - 2 2 - 0xE8 * PC87364 - 3 3 - 0xE4 * PC87365 11 3 3 2 0xE5 * PC87366 11 3 3 3-4 0xE9 * * This driver assumes that no more than one chip is present, and one of * the standard Super-I/O addresses is used (0x2E/0x2F or 0x4E/0x4F).
*/
staticint init = 1;
module_param(init, int, 0);
MODULE_PARM_DESC(init, "Chip initialization level:\n" " 0: None\n" "*1: Forcibly enable internal voltage and temperature channels, except in9\n" " 2: Forcibly enable all voltage and temperature channels, except in9\n" " 3: Forcibly enable all voltage and temperature channels, including in9");
struct pc87360_data { constchar *name; struct device *hwmon_dev; struct mutex lock; struct mutex update_lock; bool valid; /* true if following fields are valid */ unsignedlong last_updated; /* In jiffies */
int address[3];
u8 fannr, innr, tempnr;
u8 fan[3]; /* Register value */
u8 fan_min[3]; /* Register value */
u8 fan_status[3]; /* Register value */
u8 pwm[3]; /* Register value */
u16 fan_conf; /* Configuration register values, combined */
u16 in_vref; /* 1 mV/bit */
u8 in[14]; /* Register value */
u8 in_min[14]; /* Register value */
u8 in_max[14]; /* Register value */
u8 in_crit[3]; /* Register value */
u8 in_status[14]; /* Register value */
u16 in_alarms; /* Register values, combined, masked */
u8 vid_conf; /* Configuration register value */
u8 vrm;
u8 vid; /* Register value */
s8 temp[3]; /* Register value */
s8 temp_min[3]; /* Register value */
s8 temp_max[3]; /* Register value */
s8 temp_crit[3]; /* Register value */
u8 temp_status[3]; /* Register value */
u8 temp_alarms; /* Register value, masked */
};
/* * ldi is the logical device index * bank is for voltages and temperatures only
*/ staticint pc87360_read_value(struct pc87360_data *data, u8 ldi, u8 bank,
u8 reg)
{ int res;
mutex_lock(&(data->lock)); if (bank != NO_BANK)
outb_p(bank, data->address[ldi] + PC87365_REG_BANK);
res = inb_p(data->address[ldi] + reg);
mutex_unlock(&(data->lock));
/* Increase clock divider if needed and possible */ if ((data->fan_status[nr] & 0x04) /* overflow flag */
|| (data->fan[nr] >= 224)) { /* next to overflow */ if ((data->fan_status[nr] & 0x60) != 0x60) {
data->fan_status[nr] += 0x20;
data->fan_min[nr] >>= 1;
data->fan[nr] >>= 1;
dev_dbg(dev, "Increasing clock divider to %d for fan %d\n",
FAN_DIV_FROM_REG(data->fan_status[nr]), nr + 1);
}
} else { /* Decrease clock divider if possible */ while (!(data->fan_min[nr] & 0x80) /* min "nails" divider */
&& data->fan[nr] < 85 /* bad accuracy */
&& (data->fan_status[nr] & 0x60) != 0x00) {
data->fan_status[nr] -= 0x20;
data->fan_min[nr] <<= 1;
data->fan[nr] <<= 1;
dev_dbg(dev, "Decreasing clock divider to %d for fan %d\n",
FAN_DIV_FROM_REG(data->fan_status[nr]),
nr + 1);
}
}
/* Write new fan min if it changed */ if (old_min != data->fan_min[nr]) {
pc87360_write_value(data, LD_FAN, NO_BANK,
PC87360_REG_FAN_MIN(nr),
data->fan_min[nr]);
}
}
/* Fans */ for (i = 0; i < data->fannr; i++) { if (FAN_CONFIG_MONITOR(data->fan_conf, i)) {
data->fan_status[i] =
pc87360_read_value(data, LD_FAN,
NO_BANK, PC87360_REG_FAN_STATUS(i));
data->fan[i] = pc87360_read_value(data, LD_FAN,
NO_BANK, PC87360_REG_FAN(i));
data->fan_min[i] = pc87360_read_value(data,
LD_FAN, NO_BANK,
PC87360_REG_FAN_MIN(i)); /* Change clock divider if needed */
pc87360_autodiv(dev, i); /* Clear bits and write new divider */
pc87360_write_value(data, LD_FAN, NO_BANK,
PC87360_REG_FAN_STATUS(i),
data->fan_status[i]);
} if (FAN_CONFIG_CONTROL(data->fan_conf, i))
data->pwm[i] = pc87360_read_value(data, LD_FAN,
NO_BANK, PC87360_REG_PWM(i));
}
/* Voltages */ /* * The min() below does not have any practical meaning and is * only needed to silence a warning observed with gcc 12+.
*/ for (i = 0; i < min(data->innr, ARRAY_SIZE(data->in)); i++) {
data->in_status[i] = pc87360_read_value(data, LD_IN, i,
PC87365_REG_IN_STATUS); /* Clear bits */
pc87360_write_value(data, LD_IN, i,
PC87365_REG_IN_STATUS,
data->in_status[i]); if ((data->in_status[i] & CHAN_READY) == CHAN_READY) {
data->in[i] = pc87360_read_value(data, LD_IN,
i, PC87365_REG_IN);
} if (data->in_status[i] & CHAN_ENA) {
data->in_min[i] = pc87360_read_value(data,
LD_IN, i,
PC87365_REG_IN_MIN);
data->in_max[i] = pc87360_read_value(data,
LD_IN, i,
PC87365_REG_IN_MAX); if (i >= 11)
data->in_crit[i-11] =
pc87360_read_value(data, LD_IN,
i, PC87365_REG_TEMP_CRIT);
}
} if (data->innr) {
data->in_alarms = pc87360_read_value(data, LD_IN,
NO_BANK, PC87365_REG_IN_ALARMS1)
| ((pc87360_read_value(data, LD_IN,
NO_BANK, PC87365_REG_IN_ALARMS2)
& 0x07) << 8);
data->vid = (data->vid_conf & 0xE0) ?
pc87360_read_value(data, LD_IN,
NO_BANK, PC87365_REG_VID) : 0x1F;
}
/* Temperatures */ for (i = 0; i < data->tempnr; i++) {
data->temp_status[i] = pc87360_read_value(data,
LD_TEMP, i,
PC87365_REG_TEMP_STATUS); /* Clear bits */
pc87360_write_value(data, LD_TEMP, i,
PC87365_REG_TEMP_STATUS,
data->temp_status[i]); if ((data->temp_status[i] & CHAN_READY) == CHAN_READY) {
data->temp[i] = pc87360_read_value(data,
LD_TEMP, i,
PC87365_REG_TEMP);
} if (data->temp_status[i] & CHAN_ENA) {
data->temp_min[i] = pc87360_read_value(data,
LD_TEMP, i,
PC87365_REG_TEMP_MIN);
data->temp_max[i] = pc87360_read_value(data,
LD_TEMP, i,
PC87365_REG_TEMP_MAX);
data->temp_crit[i] = pc87360_read_value(data,
LD_TEMP, i,
PC87365_REG_TEMP_CRIT);
}
} if (data->tempnr) {
data->temp_alarms = pc87360_read_value(data, LD_TEMP,
NO_BANK, PC87365_REG_TEMP_ALARMS)
& 0x3F;
}
/* * show_in_min/max_alarm() reads data from the per-channel status * register (sec 11.5.12), not the vin event status registers (sec * 11.5.2) that (legacy) show_in_alarm() resds (via data->in_alarms)
*/
/* * the +11 term below reflects the fact that VLM units 11,12,13 are * used in the chip to measure voltage across the thermistors
*/ staticstruct sensor_device_attribute therm_input[] = {
SENSOR_ATTR_RO(temp4_input, therm_input, 0 + 11),
SENSOR_ATTR_RO(temp5_input, therm_input, 1 + 11),
SENSOR_ATTR_RO(temp6_input, therm_input, 2 + 11),
};
nr = min(data->innr, 11); for (i = 0; i < nr; i++) {
reg = pc87360_read_value(data, LD_IN, i,
PC87365_REG_IN_STATUS);
dev_dbg(&pdev->dev, "bios in%d status:0x%02x\n", i, reg); if (init >= init_in[i]) { /* Forcibly enable voltage channel */ if (!(reg & CHAN_ENA)) {
dev_dbg(&pdev->dev, "Forcibly enabling in%d\n",
i);
pc87360_write_value(data, LD_IN, i,
PC87365_REG_IN_STATUS,
(reg & 0x68) | 0x87);
}
}
}
/* * We can't blindly trust the Super-I/O space configuration bit, * most BIOS won't set it properly
*/
dev_dbg(&pdev->dev, "bios thermistors:%d\n", use_thermistors); for (i = 11; i < data->innr; i++) {
reg = pc87360_read_value(data, LD_IN, i,
PC87365_REG_TEMP_STATUS);
use_thermistors = use_thermistors || (reg & CHAN_ENA); /* thermistors are temp[4-6], measured on vin[11-14] */
dev_dbg(&pdev->dev, "bios temp%d_status:0x%02x\n", i-7, reg);
}
dev_dbg(&pdev->dev, "using thermistors:%d\n", use_thermistors);
i = use_thermistors ? 2 : 0; for (; i < data->tempnr; i++) {
reg = pc87360_read_value(data, LD_TEMP, i,
PC87365_REG_TEMP_STATUS);
dev_dbg(&pdev->dev, "bios temp%d_status:0x%02x\n", i + 1, reg); if (init >= init_temp[i]) { /* Forcibly enable temperature channel */ if (!(reg & CHAN_ENA)) {
dev_dbg(&pdev->dev, "Forcibly enabling temp%d\n", i + 1);
pc87360_write_value(data, LD_TEMP, i,
PC87365_REG_TEMP_STATUS,
0xCF);
}
}
}
if (use_thermistors) { for (i = 11; i < data->innr; i++) { if (init >= init_in[i]) { /* * The pin may already be used by thermal * diodes
*/
reg = pc87360_read_value(data, LD_TEMP,
(i - 11) / 2, PC87365_REG_TEMP_STATUS); if (reg & CHAN_ENA) {
dev_dbg(&pdev->dev, "Skipping temp%d, pin already in use by temp%d\n",
i - 7, (i - 11) / 2); continue;
}
/* Forcibly enable thermistor channel */
reg = pc87360_read_value(data, LD_IN, i,
PC87365_REG_IN_STATUS); if (!(reg & CHAN_ENA)) {
dev_dbg(&pdev->dev, "Forcibly enabling temp%d\n",
i - 7);
pc87360_write_value(data, LD_IN, i,
PC87365_REG_TEMP_STATUS,
(reg & 0x60) | 0x8F);
}
}
}
}
if (init >= 2) { /* Chip config as documented by National Semi. */
pc87360_write_value(data, LD_TEMP, 0xF, 0xA, 0x08); /* * We voluntarily omit the bank here, in case the * sequence itself matters. It shouldn't be a problem, * since nobody else is supposed to access the * device at that point.
*/
pc87360_write_value(data, LD_TEMP, NO_BANK, 0xB, 0x04);
pc87360_write_value(data, LD_TEMP, NO_BANK, 0xC, 0x35);
pc87360_write_value(data, LD_TEMP, NO_BANK, 0xD, 0x05);
pc87360_write_value(data, LD_TEMP, NO_BANK, 0xE, 0x05);
}
}
}
staticint pc87360_probe(struct platform_device *pdev)
{ int i; struct pc87360_data *data; int err = 0; constchar *name; int use_thermistors = 0; struct device *dev = &pdev->dev;
data = devm_kzalloc(dev, sizeof(struct pc87360_data), GFP_KERNEL); if (!data) return -ENOMEM;
switch (devid) { default:
name = "pc87360";
data->fannr = 2; break; case 0xe8:
name = "pc87363";
data->fannr = 2; break; case 0xe4:
name = "pc87364";
data->fannr = 3; break; case 0xe5:
name = "pc87365";
data->fannr = extra_isa[0] ? 3 : 0;
data->innr = extra_isa[1] ? 11 : 0;
data->tempnr = extra_isa[2] ? 2 : 0; break; case 0xe9:
name = "pc87366";
data->fannr = extra_isa[0] ? 3 : 0;
data->innr = extra_isa[1] ? 14 : 0;
data->tempnr = extra_isa[2] ? 3 : 0; break;
}
for (i = 0; i < LDNI_MAX; i++) {
data->address[i] = extra_isa[i]; if (data->address[i]
&& !devm_request_region(dev, extra_isa[i], PC87360_EXTENT,
DRIVER_NAME)) {
dev_err(dev, "Region 0x%x-0x%x already in use!\n",
extra_isa[i], extra_isa[i]+PC87360_EXTENT-1); return -EBUSY;
}
}
/* Retrieve the fans configuration from Super-I/O space */ if (data->fannr)
data->fan_conf = confreg[0] | (confreg[1] << 8);
/* * Use the correct reference voltage * Unless both the VLM and the TMS logical devices agree to * use an external Vref, the internal one is used.
*/ if (data->innr) {
i = pc87360_read_value(data, LD_IN, NO_BANK,
PC87365_REG_IN_CONFIG); if (data->tempnr) {
i &= pc87360_read_value(data, LD_TEMP, NO_BANK,
PC87365_REG_TEMP_CONFIG);
}
data->in_vref = (i&0x02) ? 3025 : 2966;
dev_dbg(dev, "Using %s reference voltage\n",
(i&0x02) ? "external" : "internal");
/* Fan clock dividers may be needed before any data is read */ for (i = 0; i < data->fannr; i++) { if (FAN_CONFIG_MONITOR(data->fan_conf, i))
data->fan_status[i] = pc87360_read_value(data,
LD_FAN, NO_BANK,
PC87360_REG_FAN_STATUS(i));
}
if (init > 0) { if (devid == 0xe9 && data->address[1]) /* PC87366 */
use_thermistors = confreg[2] & 0x40;
pc87360_init_device(pdev, use_thermistors);
}
/* Register all-or-nothing sysfs groups */
if (data->innr) {
err = sysfs_create_group(&dev->kobj, &pc8736x_vin_group); if (err) goto error;
}
if (data->innr == 14) {
err = sysfs_create_group(&dev->kobj, &pc8736x_therm_group); if (err) goto error;
}
/* create device attr-files for varying sysfs groups */
if (data->tempnr) { for (i = 0; i < data->tempnr; i++) {
err = sysfs_create_group(&dev->kobj,
&pc8736x_temp_attr_group[i]); if (err) goto error;
}
err = device_create_file(dev, &dev_attr_alarms_temp); if (err) goto error;
}
for (i = 0; i < data->fannr; i++) { if (FAN_CONFIG_MONITOR(data->fan_conf, i)) {
err = sysfs_create_group(&dev->kobj,
&pc8736x_fan_attr_group[i]); if (err) goto error;
} if (FAN_CONFIG_CONTROL(data->fan_conf, i)) {
err = device_create_file(dev, &pwm[i].dev_attr); if (err) goto error;
}
}
err = device_create_file(dev, &dev_attr_name); if (err) goto error;
staticint __init pc87360_find(int sioaddr, u8 *devid, unsignedshort *addresses)
{
u16 val; int i; int nrdev; /* logical device count */
/* No superio_enter */
/* Identify device */
val = force_id ? force_id : superio_inb(sioaddr, DEVID); switch (val) { case 0xE1: /* PC87360 */ case 0xE8: /* PC87363 */ case 0xE4: /* PC87364 */
nrdev = 1; break; case 0xE5: /* PC87365 */ case 0xE9: /* PC87366 */
nrdev = 3; break; default:
superio_exit(sioaddr); return -ENODEV;
} /* Remember the device id */
*devid = val;
for (i = 0; i < nrdev; i++) { /* select logical device */
superio_outb(sioaddr, DEV, logdev[i]);
val = superio_inb(sioaddr, ACT); if (!(val & 0x01)) {
pr_info("Device 0x%02x not activated\n", logdev[i]); continue;
}
val = (superio_inb(sioaddr, BASE) << 8)
| superio_inb(sioaddr, BASE + 1); if (!val) {
pr_info("Base address not set for device 0x%02x\n",
logdev[i]); continue;
}
addresses[i] = val;
if (i == 0) { /* Fans */
confreg[0] = superio_inb(sioaddr, 0xF0);
confreg[1] = superio_inb(sioaddr, 0xF1);
pr_debug("Fan %d: mon=%d ctrl=%d inv=%d\n", 1,
(confreg[0] >> 2) & 1, (confreg[0] >> 3) & 1,
(confreg[0] >> 4) & 1);
pr_debug("Fan %d: mon=%d ctrl=%d inv=%d\n", 2,
(confreg[0] >> 5) & 1, (confreg[0] >> 6) & 1,
(confreg[0] >> 7) & 1);
pr_debug("Fan %d: mon=%d ctrl=%d inv=%d\n", 3,
confreg[1] & 1, (confreg[1] >> 1) & 1,
(confreg[1] >> 2) & 1);
} elseif (i == 1) { /* Voltages */ /* Are we using thermistors? */ if (*devid == 0xE9) { /* PC87366 */ /* * These registers are not logical-device * specific, just that we won't need them if * we don't use the VLM device
*/
confreg[2] = superio_inb(sioaddr, 0x2B);
confreg[3] = superio_inb(sioaddr, 0x25);
if (confreg[2] & 0x40) {
pr_info("Using thermistors for temperature monitoring\n");
} if (confreg[3] & 0xE0) {
pr_info("VID inputs routed (mode %u)\n",
confreg[3] >> 5);
}
}
}
}
superio_exit(sioaddr); return 0;
}
staticint __init pc87360_device_add(unsignedshort address)
{ struct resource res[3]; int err, i, res_count;
¤ 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.0.44Bemerkung:
(vorverarbeitet)
¤
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.