116216333SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 229fa06c1SRudolf Marek /* 329fa06c1SRudolf Marek * k8temp.c - Linux kernel module for hardware monitoring 429fa06c1SRudolf Marek * 57188cc66SJean Delvare * Copyright (C) 2006 Rudolf Marek <r.marek@assembler.cz> 629fa06c1SRudolf Marek * 729fa06c1SRudolf Marek * Inspired from the w83785 and amd756 drivers. 829fa06c1SRudolf Marek */ 929fa06c1SRudolf Marek 1029fa06c1SRudolf Marek #include <linux/module.h> 1129fa06c1SRudolf Marek #include <linux/init.h> 1229fa06c1SRudolf Marek #include <linux/slab.h> 1329fa06c1SRudolf Marek #include <linux/pci.h> 1429fa06c1SRudolf Marek #include <linux/hwmon.h> 1529fa06c1SRudolf Marek #include <linux/err.h> 1629fa06c1SRudolf Marek #include <linux/mutex.h> 17bb9a35f2SAndreas Herrmann #include <asm/processor.h> 1829fa06c1SRudolf Marek 1929fa06c1SRudolf Marek #define TEMP_FROM_REG(val) (((((val) >> 16) & 0xff) - 49) * 1000) 2029fa06c1SRudolf Marek #define REG_TEMP 0xe4 2129fa06c1SRudolf Marek #define SEL_PLACE 0x40 2229fa06c1SRudolf Marek #define SEL_CORE 0x04 2329fa06c1SRudolf Marek 2429fa06c1SRudolf Marek struct k8temp_data { 2529fa06c1SRudolf Marek struct mutex update_lock; 2629fa06c1SRudolf Marek 2729fa06c1SRudolf Marek /* registers values */ 2893092a64SFrans Meulenbroeks u8 sensorsp; /* sensor presence bits - SEL_CORE, SEL_PLACE */ 29a2e066bbSAndreas Herrmann u8 swap_core_select; /* meaning of SEL_CORE is inverted */ 3076ff08daSAndreas Herrmann u32 temp_offset; 3129fa06c1SRudolf Marek }; 3229fa06c1SRudolf Marek 33cd9bb056SJingoo Han static const struct pci_device_id k8temp_ids[] = { 3429fa06c1SRudolf Marek { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_K8_NB_MISC) }, 3529fa06c1SRudolf Marek { 0 }, 3629fa06c1SRudolf Marek }; 37b17ebc94SJean Delvare MODULE_DEVICE_TABLE(pci, k8temp_ids); 38b17ebc94SJean Delvare 396c931ae1SBill Pemberton static int is_rev_g_desktop(u8 model) 40a05e93f3SAndreas Herrmann { 41a05e93f3SAndreas Herrmann u32 brandidx; 42a05e93f3SAndreas Herrmann 43a05e93f3SAndreas Herrmann if (model < 0x69) 44a05e93f3SAndreas Herrmann return 0; 45a05e93f3SAndreas Herrmann 46a05e93f3SAndreas Herrmann if (model == 0xc1 || model == 0x6c || model == 0x7c) 47a05e93f3SAndreas Herrmann return 0; 48a05e93f3SAndreas Herrmann 49a05e93f3SAndreas Herrmann /* 50a05e93f3SAndreas Herrmann * Differentiate between AM2 and ASB1. 51a05e93f3SAndreas Herrmann * See "Constructing the processor Name String" in "Revision 52a05e93f3SAndreas Herrmann * Guide for AMD NPT Family 0Fh Processors" (33610). 53a05e93f3SAndreas Herrmann */ 54a05e93f3SAndreas Herrmann brandidx = cpuid_ebx(0x80000001); 55a05e93f3SAndreas Herrmann brandidx = (brandidx >> 9) & 0x1f; 56a05e93f3SAndreas Herrmann 57a05e93f3SAndreas Herrmann /* Single core */ 58a05e93f3SAndreas Herrmann if ((model == 0x6f || model == 0x7f) && 59a05e93f3SAndreas Herrmann (brandidx == 0x7 || brandidx == 0x9 || brandidx == 0xc)) 60a05e93f3SAndreas Herrmann return 0; 61a05e93f3SAndreas Herrmann 62a05e93f3SAndreas Herrmann /* Dual core */ 63a05e93f3SAndreas Herrmann if (model == 0x6b && 64a05e93f3SAndreas Herrmann (brandidx == 0xb || brandidx == 0xc)) 65a05e93f3SAndreas Herrmann return 0; 66a05e93f3SAndreas Herrmann 67a05e93f3SAndreas Herrmann return 1; 68a05e93f3SAndreas Herrmann } 69a05e93f3SAndreas Herrmann 703b07a702SRobert Karszniewicz static umode_t 713b07a702SRobert Karszniewicz k8temp_is_visible(const void *drvdata, enum hwmon_sensor_types type, 723b07a702SRobert Karszniewicz u32 attr, int channel) 733b07a702SRobert Karszniewicz { 743b07a702SRobert Karszniewicz const struct k8temp_data *data = drvdata; 753b07a702SRobert Karszniewicz 763b07a702SRobert Karszniewicz if ((channel & 1) && !(data->sensorsp & SEL_PLACE)) 773b07a702SRobert Karszniewicz return 0; 783b07a702SRobert Karszniewicz 793b07a702SRobert Karszniewicz if ((channel & 2) && !(data->sensorsp & SEL_CORE)) 803b07a702SRobert Karszniewicz return 0; 813b07a702SRobert Karszniewicz 823b07a702SRobert Karszniewicz return 0444; 833b07a702SRobert Karszniewicz } 843b07a702SRobert Karszniewicz 853b07a702SRobert Karszniewicz static int 863b07a702SRobert Karszniewicz k8temp_read(struct device *dev, enum hwmon_sensor_types type, 873b07a702SRobert Karszniewicz u32 attr, int channel, long *val) 883b07a702SRobert Karszniewicz { 893b07a702SRobert Karszniewicz struct k8temp_data *data = dev_get_drvdata(dev); 903b07a702SRobert Karszniewicz struct pci_dev *pdev = to_pci_dev(dev->parent); 913b07a702SRobert Karszniewicz int core, place; 923b07a702SRobert Karszniewicz u32 temp; 933b07a702SRobert Karszniewicz u8 tmp; 943b07a702SRobert Karszniewicz 953b07a702SRobert Karszniewicz core = (channel >> 1) & 1; 963b07a702SRobert Karszniewicz place = channel & 1; 973b07a702SRobert Karszniewicz 983b07a702SRobert Karszniewicz core ^= data->swap_core_select; 993b07a702SRobert Karszniewicz 1003b07a702SRobert Karszniewicz mutex_lock(&data->update_lock); 1013b07a702SRobert Karszniewicz pci_read_config_byte(pdev, REG_TEMP, &tmp); 1023b07a702SRobert Karszniewicz tmp &= ~(SEL_PLACE | SEL_CORE); 1033b07a702SRobert Karszniewicz if (core) 1043b07a702SRobert Karszniewicz tmp |= SEL_CORE; 1053b07a702SRobert Karszniewicz if (place) 1063b07a702SRobert Karszniewicz tmp |= SEL_PLACE; 1073b07a702SRobert Karszniewicz pci_write_config_byte(pdev, REG_TEMP, tmp); 1083b07a702SRobert Karszniewicz pci_read_config_dword(pdev, REG_TEMP, &temp); 1093b07a702SRobert Karszniewicz mutex_unlock(&data->update_lock); 1103b07a702SRobert Karszniewicz 1113b07a702SRobert Karszniewicz *val = TEMP_FROM_REG(temp) + data->temp_offset; 1123b07a702SRobert Karszniewicz 1133b07a702SRobert Karszniewicz return 0; 1143b07a702SRobert Karszniewicz } 1153b07a702SRobert Karszniewicz 1163b07a702SRobert Karszniewicz static const struct hwmon_ops k8temp_ops = { 1173b07a702SRobert Karszniewicz .is_visible = k8temp_is_visible, 1183b07a702SRobert Karszniewicz .read = k8temp_read, 1193b07a702SRobert Karszniewicz }; 1203b07a702SRobert Karszniewicz 121*55f44663SKrzysztof Kozlowski static const struct hwmon_channel_info * const k8temp_info[] = { 1223b07a702SRobert Karszniewicz HWMON_CHANNEL_INFO(temp, 1233b07a702SRobert Karszniewicz HWMON_T_INPUT, HWMON_T_INPUT, HWMON_T_INPUT, HWMON_T_INPUT), 1243b07a702SRobert Karszniewicz NULL 1253b07a702SRobert Karszniewicz }; 1263b07a702SRobert Karszniewicz 1273b07a702SRobert Karszniewicz static const struct hwmon_chip_info k8temp_chip_info = { 1283b07a702SRobert Karszniewicz .ops = &k8temp_ops, 1293b07a702SRobert Karszniewicz .info = k8temp_info, 1303b07a702SRobert Karszniewicz }; 1313b07a702SRobert Karszniewicz 1326c931ae1SBill Pemberton static int k8temp_probe(struct pci_dev *pdev, 13329fa06c1SRudolf Marek const struct pci_device_id *id) 13429fa06c1SRudolf Marek { 13529fa06c1SRudolf Marek u8 scfg; 13629fa06c1SRudolf Marek u32 temp; 137bb9a35f2SAndreas Herrmann u8 model, stepping; 13829fa06c1SRudolf Marek struct k8temp_data *data; 1393b07a702SRobert Karszniewicz struct device *hwmon_dev; 14029fa06c1SRudolf Marek 141a0d44cbcSGuenter Roeck data = devm_kzalloc(&pdev->dev, sizeof(struct k8temp_data), GFP_KERNEL); 142a0d44cbcSGuenter Roeck if (!data) 143a0d44cbcSGuenter Roeck return -ENOMEM; 14429fa06c1SRudolf Marek 145bb9a35f2SAndreas Herrmann model = boot_cpu_data.x86_model; 146b399151cSJia Zhang stepping = boot_cpu_data.x86_stepping; 147bb9a35f2SAndreas Herrmann 148bb9a35f2SAndreas Herrmann /* feature available since SH-C0, exclude older revisions */ 149a0d44cbcSGuenter Roeck if ((model == 4 && stepping == 0) || 150a0d44cbcSGuenter Roeck (model == 5 && stepping <= 1)) 151a0d44cbcSGuenter Roeck return -ENODEV; 152bb9a35f2SAndreas Herrmann 153a2e066bbSAndreas Herrmann /* 154a2e066bbSAndreas Herrmann * AMD NPT family 0fh, i.e. RevF and RevG: 155a2e066bbSAndreas Herrmann * meaning of SEL_CORE bit is inverted 156a2e066bbSAndreas Herrmann */ 157bb9a35f2SAndreas Herrmann if (model >= 0x40) { 158a2e066bbSAndreas Herrmann data->swap_core_select = 1; 159b55f3757SGuenter Roeck dev_warn(&pdev->dev, 160b55f3757SGuenter Roeck "Temperature readouts might be wrong - check erratum #141\n"); 161bb9a35f2SAndreas Herrmann } 162bb9a35f2SAndreas Herrmann 16376ff08daSAndreas Herrmann /* 164628b4504SAndreas Herrmann * RevG desktop CPUs (i.e. no socket S1G1 or ASB1 parts) need 165628b4504SAndreas Herrmann * additional offset, otherwise reported temperature is below 166d535bad9SAndreas Herrmann * ambient temperature 16776ff08daSAndreas Herrmann */ 168628b4504SAndreas Herrmann if (is_rev_g_desktop(model)) 16976ff08daSAndreas Herrmann data->temp_offset = 21000; 170bb9a35f2SAndreas Herrmann 17129fa06c1SRudolf Marek pci_read_config_byte(pdev, REG_TEMP, &scfg); 17229fa06c1SRudolf Marek scfg &= ~(SEL_PLACE | SEL_CORE); /* Select sensor 0, core0 */ 17329fa06c1SRudolf Marek pci_write_config_byte(pdev, REG_TEMP, scfg); 17429fa06c1SRudolf Marek pci_read_config_byte(pdev, REG_TEMP, &scfg); 17529fa06c1SRudolf Marek 17629fa06c1SRudolf Marek if (scfg & (SEL_PLACE | SEL_CORE)) { 17729fa06c1SRudolf Marek dev_err(&pdev->dev, "Configuration bit(s) stuck at 1!\n"); 178a0d44cbcSGuenter Roeck return -ENODEV; 17929fa06c1SRudolf Marek } 18029fa06c1SRudolf Marek 18129fa06c1SRudolf Marek scfg |= (SEL_PLACE | SEL_CORE); 18229fa06c1SRudolf Marek pci_write_config_byte(pdev, REG_TEMP, scfg); 18329fa06c1SRudolf Marek 18429fa06c1SRudolf Marek /* now we know if we can change core and/or sensor */ 18529fa06c1SRudolf Marek pci_read_config_byte(pdev, REG_TEMP, &data->sensorsp); 18629fa06c1SRudolf Marek 18729fa06c1SRudolf Marek if (data->sensorsp & SEL_PLACE) { 18829fa06c1SRudolf Marek scfg &= ~SEL_CORE; /* Select sensor 1, core0 */ 18929fa06c1SRudolf Marek pci_write_config_byte(pdev, REG_TEMP, scfg); 19029fa06c1SRudolf Marek pci_read_config_dword(pdev, REG_TEMP, &temp); 19129fa06c1SRudolf Marek scfg |= SEL_CORE; /* prepare for next selection */ 19293092a64SFrans Meulenbroeks if (!((temp >> 16) & 0xff)) /* if temp is 0 -49C is unlikely */ 19329fa06c1SRudolf Marek data->sensorsp &= ~SEL_PLACE; 19429fa06c1SRudolf Marek } 19529fa06c1SRudolf Marek 19629fa06c1SRudolf Marek if (data->sensorsp & SEL_CORE) { 19729fa06c1SRudolf Marek scfg &= ~SEL_PLACE; /* Select sensor 0, core1 */ 19829fa06c1SRudolf Marek pci_write_config_byte(pdev, REG_TEMP, scfg); 19929fa06c1SRudolf Marek pci_read_config_dword(pdev, REG_TEMP, &temp); 20093092a64SFrans Meulenbroeks if (!((temp >> 16) & 0xff)) /* if temp is 0 -49C is unlikely */ 20129fa06c1SRudolf Marek data->sensorsp &= ~SEL_CORE; 20229fa06c1SRudolf Marek } 20329fa06c1SRudolf Marek 20429fa06c1SRudolf Marek mutex_init(&data->update_lock); 20529fa06c1SRudolf Marek 2063b07a702SRobert Karszniewicz hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, 2073b07a702SRobert Karszniewicz "k8temp", 2083b07a702SRobert Karszniewicz data, 2093b07a702SRobert Karszniewicz &k8temp_chip_info, 2103b07a702SRobert Karszniewicz NULL); 21129fa06c1SRudolf Marek 2123b07a702SRobert Karszniewicz return PTR_ERR_OR_ZERO(hwmon_dev); 21329fa06c1SRudolf Marek } 21429fa06c1SRudolf Marek 21529fa06c1SRudolf Marek static struct pci_driver k8temp_driver = { 21629fa06c1SRudolf Marek .name = "k8temp", 21729fa06c1SRudolf Marek .id_table = k8temp_ids, 21829fa06c1SRudolf Marek .probe = k8temp_probe, 21929fa06c1SRudolf Marek }; 22029fa06c1SRudolf Marek 221f71f5a55SAxel Lin module_pci_driver(k8temp_driver); 22229fa06c1SRudolf Marek 2237188cc66SJean Delvare MODULE_AUTHOR("Rudolf Marek <r.marek@assembler.cz>"); 22429fa06c1SRudolf Marek MODULE_DESCRIPTION("AMD K8 core temperature monitor"); 22529fa06c1SRudolf Marek MODULE_LICENSE("GPL"); 226