diff options
Diffstat (limited to 'drivers/hwmon/fam15h_power.c')
| -rw-r--r-- | drivers/hwmon/fam15h_power.c | 126 |
1 files changed, 82 insertions, 44 deletions
diff --git a/drivers/hwmon/fam15h_power.c b/drivers/hwmon/fam15h_power.c index 523f8fb9e7d..6040121a405 100644 --- a/drivers/hwmon/fam15h_power.c +++ b/drivers/hwmon/fam15h_power.c @@ -2,7 +2,7 @@ * fam15h_power.c - AMD Family 15h processor power monitoring * * Copyright (c) 2011 Advanced Micro Devices, Inc. - * Author: Andreas Herrmann <andreas.herrmann3@amd.com> + * Author: Andreas Herrmann <herrmann.der.user@googlemail.com> * * * This driver is free software; you can redistribute it and/or @@ -28,9 +28,12 @@ #include <asm/processor.h> MODULE_DESCRIPTION("AMD Family 15h CPU processor power monitor"); -MODULE_AUTHOR("Andreas Herrmann <andreas.herrmann3@amd.com>"); +MODULE_AUTHOR("Andreas Herrmann <herrmann.der.user@googlemail.com>"); MODULE_LICENSE("GPL"); +/* Family 16h Northbridge's function 4 PCI ID */ +#define PCI_DEVICE_ID_AMD_16H_NB_F4 0x1534 + /* D18F3 */ #define REG_NORTHBRIDGE_CAP 0xe8 @@ -60,15 +63,16 @@ static ssize_t show_power(struct device *dev, pci_bus_read_config_dword(f4->bus, PCI_DEVFN(PCI_SLOT(f4->devfn), 5), REG_TDP_RUNNING_AVERAGE, &val); running_avg_capture = (val >> 4) & 0x3fffff; - running_avg_capture = sign_extend32(running_avg_capture, 22); - running_avg_range = val & 0xf; + running_avg_capture = sign_extend32(running_avg_capture, 21); + running_avg_range = (val & 0xf) + 1; pci_bus_read_config_dword(f4->bus, PCI_DEVFN(PCI_SLOT(f4->devfn), 5), REG_TDP_LIMIT3, &val); tdp_limit = val >> 16; - curr_pwr_watts = tdp_limit + data->base_tdp - - (s32)(running_avg_capture >> (running_avg_range + 1)); + curr_pwr_watts = ((u64)(tdp_limit + + data->base_tdp)) << running_avg_range; + curr_pwr_watts -= running_avg_capture; curr_pwr_watts *= data->tdp_to_watts; /* @@ -78,7 +82,7 @@ static ssize_t show_power(struct device *dev, * scaling factor 1/(2^16). For conversion we use * (10^6)/(2^16) = 15625/(2^10) */ - curr_pwr_watts = (curr_pwr_watts * 15625) >> 10; + curr_pwr_watts = (curr_pwr_watts * 15625) >> (10 + running_avg_range); return sprintf(buf, "%u\n", (unsigned int) curr_pwr_watts); } static DEVICE_ATTR(power1_input, S_IRUGO, show_power, NULL); @@ -110,7 +114,7 @@ static const struct attribute_group fam15h_power_attr_group = { .attrs = fam15h_power_attrs, }; -static bool __devinit fam15h_power_is_internal_node0(struct pci_dev *f4) +static bool fam15h_power_is_internal_node0(struct pci_dev *f4) { u32 val; @@ -122,7 +126,52 @@ static bool __devinit fam15h_power_is_internal_node0(struct pci_dev *f4) return true; } -static void __devinit fam15h_power_init_data(struct pci_dev *f4, +/* + * Newer BKDG versions have an updated recommendation on how to properly + * initialize the running average range (was: 0xE, now: 0x9). This avoids + * counter saturations resulting in bogus power readings. + * We correct this value ourselves to cope with older BIOSes. + */ +static const struct pci_device_id affected_device[] = { + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_15H_NB_F4) }, + { 0 } +}; + +static void tweak_runavg_range(struct pci_dev *pdev) +{ + u32 val; + + /* + * let this quirk apply only to the current version of the + * northbridge, since future versions may change the behavior + */ + if (!pci_match_id(affected_device, pdev)) + return; + + pci_bus_read_config_dword(pdev->bus, + PCI_DEVFN(PCI_SLOT(pdev->devfn), 5), + REG_TDP_RUNNING_AVERAGE, &val); + if ((val & 0xf) != 0xe) + return; + + val &= ~0xf; + val |= 0x9; + pci_bus_write_config_dword(pdev->bus, + PCI_DEVFN(PCI_SLOT(pdev->devfn), 5), + REG_TDP_RUNNING_AVERAGE, val); +} + +#ifdef CONFIG_PM +static int fam15h_power_resume(struct pci_dev *pdev) +{ + tweak_runavg_range(pdev); + return 0; +} +#else +#define fam15h_power_resume NULL +#endif + +static void fam15h_power_init_data(struct pci_dev *f4, struct fam15h_power_data *data) { u32 val; @@ -140,38 +189,41 @@ static void __devinit fam15h_power_init_data(struct pci_dev *f4, /* result not allowed to be >= 256W */ if ((tmp >> 16) >= 256) - dev_warn(&f4->dev, "Bogus value for ProcessorPwrWatts " - "(processor_pwr_watts>=%u)\n", + dev_warn(&f4->dev, + "Bogus value for ProcessorPwrWatts (processor_pwr_watts>=%u)\n", (unsigned int) (tmp >> 16)); /* convert to microWatt */ data->processor_pwr_watts = (tmp * 15625) >> 10; } -static int __devinit fam15h_power_probe(struct pci_dev *pdev, +static int fam15h_power_probe(struct pci_dev *pdev, const struct pci_device_id *id) { struct fam15h_power_data *data; - struct device *dev; + struct device *dev = &pdev->dev; int err; - if (!fam15h_power_is_internal_node0(pdev)) { - err = -ENODEV; - goto exit; - } + /* + * though we ignore every other northbridge, we still have to + * do the tweaking on _each_ node in MCM processors as the counters + * are working hand-in-hand + */ + tweak_runavg_range(pdev); + + if (!fam15h_power_is_internal_node0(pdev)) + return -ENODEV; + + data = devm_kzalloc(dev, sizeof(struct fam15h_power_data), GFP_KERNEL); + if (!data) + return -ENOMEM; - data = kzalloc(sizeof(struct fam15h_power_data), GFP_KERNEL); - if (!data) { - err = -ENOMEM; - goto exit; - } fam15h_power_init_data(pdev, data); - dev = &pdev->dev; dev_set_drvdata(dev, data); err = sysfs_create_group(&dev->kobj, &fam15h_power_attr_group); if (err) - goto exit_free_data; + return err; data->hwmon_dev = hwmon_device_register(dev); if (IS_ERR(data->hwmon_dev)) { @@ -183,13 +235,10 @@ static int __devinit fam15h_power_probe(struct pci_dev *pdev, exit_remove_group: sysfs_remove_group(&dev->kobj, &fam15h_power_attr_group); -exit_free_data: - kfree(data); -exit: return err; } -static void __devexit fam15h_power_remove(struct pci_dev *pdev) +static void fam15h_power_remove(struct pci_dev *pdev) { struct device *dev; struct fam15h_power_data *data; @@ -198,12 +247,11 @@ static void __devexit fam15h_power_remove(struct pci_dev *pdev) data = dev_get_drvdata(dev); hwmon_device_unregister(data->hwmon_dev); sysfs_remove_group(&dev->kobj, &fam15h_power_attr_group); - dev_set_drvdata(dev, NULL); - kfree(data); } -static DEFINE_PCI_DEVICE_TABLE(fam15h_power_id_table) = { +static const struct pci_device_id fam15h_power_id_table[] = { { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_15H_NB_F4) }, + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_16H_NB_F4) }, {} }; MODULE_DEVICE_TABLE(pci, fam15h_power_id_table); @@ -212,18 +260,8 @@ static struct pci_driver fam15h_power_driver = { .name = "fam15h_power", .id_table = fam15h_power_id_table, .probe = fam15h_power_probe, - .remove = __devexit_p(fam15h_power_remove), + .remove = fam15h_power_remove, + .resume = fam15h_power_resume, }; -static int __init fam15h_power_init(void) -{ - return pci_register_driver(&fam15h_power_driver); -} - -static void __exit fam15h_power_exit(void) -{ - pci_unregister_driver(&fam15h_power_driver); -} - -module_init(fam15h_power_init) -module_exit(fam15h_power_exit) +module_pci_driver(fam15h_power_driver); |
