diff --git a/drivers/hwmon/ambtemp.c b/drivers/hwmon/ambtemp.c new file mode 100644 index 0000000..bf38c54 --- /dev/null +++ b/drivers/hwmon/ambtemp.c @@ -0,0 +1,278 @@ +/* + * ambtemp.c - Linux kernel module for hardware monitoring + * + * Copyright (C) 2007 Jeff Garrett + * + * Inspired from the k8temp driver. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define TEMP_FROM_REG(val) ((((u16)(val)) * 500)) +#define AMBASE_LOW_REG 0x48 +#define AMBASE_HIGH_REG 0x4C +#define AMBASE_MASK 0x000000FFFFFE0000ULL +#define AMB_MAX_DEVS 64 +#define AMB_DEV_LENGTH 2048 /* Length of a single sensor region */ +#define TEMP_OFFSET ((3*256)+0x85) + +/* + * These differ from their analogues in linux/sysfs.h and linux/hwmon-sysfs.h + * by not stringifying _name. That is, _name is allowed to be runtime + * evaluable. + */ +#define __ATTR_DYNAMIC(_name,_mode,_show,_store) { \ + .attr = { .name = _name, .mode = _mode, .owner = THIS_MODULE }, \ + .show = _show, \ + .store = _store, \ +} +#define SENSOR_ATTR_DYNAMIC(_name, _mode, _show, _store, _index) \ + { .dev_attr = __ATTR_DYNAMIC(_name, _mode, _show, _store), \ + .index = _index } + + +struct ambtemp_data { + struct class_device *class_dev; + struct mutex update_lock; + const char *name; + char valid; /* zero until following fields are valid */ + unsigned long last_updated; /* in jiffies */ + + /* register values */ + void __iomem *base; + DECLARE_BITMAP(valid_sensors, AMB_MAX_DEVS); /* bitmap for which sensors are present */ + u8 temp[AMB_MAX_DEVS]; /* all possible sensors */ + + struct sensor_device_attribute temp_input[AMB_MAX_DEVS]; +}; + +static struct ambtemp_data *ambtemp_update_device(struct device *dev) +{ + struct ambtemp_data *data = dev_get_drvdata(dev); + int i; + + mutex_lock(&data->update_lock); + + if (!data->valid + || time_after(jiffies, data->last_updated + HZ)) { + + /* Read the sensors */ + for (i = 0; i < AMB_MAX_DEVS; i++) + if (test_bit(i, &data->valid_sensors)) + data->temp[i] = readb(data->base + i*AMB_DEV_LENGTH + TEMP_OFFSET); + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + return data; +} + +/* + * Sysfs stuff + */ + +static ssize_t show_name(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct ambtemp_data *data = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", data->name); +} + + +static ssize_t show_temp(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = + to_sensor_dev_attr(devattr); + int sensor = attr->index; + struct ambtemp_data *data = ambtemp_update_device(dev); + + return sprintf(buf, "%d\n", + TEMP_FROM_REG(data->temp[sensor])); +} + + +static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); + +static struct pci_device_id ambtemp_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_MCH_AMB) }, + { 0 }, +}; + +MODULE_DEVICE_TABLE(pci, ambtemp_ids); + +static int __devinit ambtemp_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + int err; + int i; + u32 low, high; + u64 addr; + unsigned long c; + char *name; + + struct ambtemp_data *data; + + /* The Error Reporting Registers we piggy back on show up at + * 10.0, 10.1, and 10.2. We only want the first function. The + * AMBASE slot on the others is wrong. + */ + if (PCI_FUNC(pdev->devfn) != 0x00) { + /* Go away */ + err = 0; + goto exit; + } + + if (!(data = kzalloc(sizeof(struct ambtemp_data), GFP_KERNEL))) { + err = -ENOMEM; + goto exit; + } + + dev_info(&pdev->dev, "Device found\n"); + dev_dbg(&pdev->dev, "Scanning for sensors...\n"); + + /* Reserve the sensor range starting at AMBASE */ + pci_read_config_dword(pdev, AMBASE_LOW_REG, &low); + pci_read_config_dword(pdev, AMBASE_HIGH_REG, &high); + + addr = ((u64)high << 32 | low) & AMBASE_MASK; + if (!addr) { + err = -EBUSY; + goto exit; + } + + data->base = ioremap(addr, AMB_MAX_DEVS * AMB_DEV_LENGTH); + if (!data->base) { + err = -EBUSY; + goto exit; + } + + for (i = 0; i < AMB_MAX_DEVS; i++) { + c = readl(data->base + i*AMB_DEV_LENGTH); + if (c != 0xffffffffl) { + set_bit(i, &data->valid_sensors); + dev_dbg(&pdev->dev, "Sensor at offset 0x%02x\n", i); + } + } + + data->name = "ambtemp"; + mutex_init(&data->update_lock); + dev_set_drvdata(&pdev->dev, data); + + /* Register sysfs hooks */ + for (i = 0; i < ARRAY_SIZE(data->temp_input); i++) + if (test_bit(i, &data->valid_sensors)) { + if (!(name = kasprintf(GFP_KERNEL, "temp%d_input", i + 1))) { + err = -ENOMEM; + goto exit_remove; + } + + data->temp_input[i] = (struct sensor_device_attribute) + SENSOR_ATTR_DYNAMIC(name, S_IRUGO, show_temp, NULL, i); + + err = device_create_file(&pdev->dev, &data->temp_input[i].dev_attr); + if (err) + goto exit_remove; + } + + err = device_create_file(&pdev->dev, &dev_attr_name); + if (err) + goto exit_remove; + + data->class_dev = hwmon_device_register(&pdev->dev); + + if (IS_ERR(data->class_dev)) { + err = PTR_ERR(data->class_dev); + goto exit_remove; + } + + return 0; + +exit_remove: + for (i = 0; i < ARRAY_SIZE(data->temp_input); i++) + if (data->temp_input[i].dev_attr.attr.name) { + device_remove_file(&pdev->dev, &data->temp_input[i].dev_attr); + kfree(data->temp_input[i].dev_attr.attr.name); + } + + device_remove_file(&pdev->dev, &dev_attr_name); + dev_set_drvdata(&pdev->dev, NULL); + kfree(data); +exit: + return err; +} + +static void __devexit ambtemp_remove(struct pci_dev *pdev) +{ + int i; + struct ambtemp_data *data = dev_get_drvdata(&pdev->dev); + + if (!data) + return; + + hwmon_device_unregister(data->class_dev); + for (i = 0; i < ARRAY_SIZE(data->temp_input); i++) + if (data->temp_input[i].dev_attr.attr.name) { + device_remove_file(&pdev->dev, &data->temp_input[i].dev_attr); + kfree(data->temp_input[i].dev_attr.attr.name); + } + + device_remove_file(&pdev->dev, &dev_attr_name); + dev_set_drvdata(&pdev->dev, NULL); + + iounmap(data->base); + kfree(data); +} + +static struct pci_driver ambtemp_driver = { + .name = "ambtemp", + .id_table = ambtemp_ids, + .probe = ambtemp_probe, + .remove = __devexit_p(ambtemp_remove), +}; + +static int __init ambtemp_init(void) +{ + return pci_register_driver(&ambtemp_driver); +} + +static void __exit ambtemp_exit(void) +{ + pci_unregister_driver(&ambtemp_driver); +} + +MODULE_AUTHOR("Jeff Garrett "); +MODULE_DESCRIPTION("Intel AMB temperature monitor"); +MODULE_LICENSE("GPL"); + +module_init(ambtemp_init) +module_exit(ambtemp_exit) diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index 4712e26..9b55239 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -2146,6 +2146,7 @@ #define PCI_VENDOR_ID_INTEL 0x8086 #define PCI_DEVICE_ID_INTEL_EESSC 0x0008 +#define PCI_DEVICE_ID_INTEL_MCH_AMB 0x25F0 #define PCI_DEVICE_ID_INTEL_PXHD_0 0x0320 #define PCI_DEVICE_ID_INTEL_PXHD_1 0x0321 #define PCI_DEVICE_ID_INTEL_PXH_0 0x0329