xref: /linux/drivers/hwmon/k8temp.c (revision bba2c3615bd6cfee7456d1130f2e6b01b3f4e9ba)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * k8temp.c - Linux kernel module for hardware monitoring
4  *
5  * Copyright (C) 2006 Rudolf Marek <r.marek@assembler.cz>
6  *
7  * Inspired from the w83785 and amd756 drivers.
8  */
9 
10 #include <linux/module.h>
11 #include <linux/init.h>
12 #include <linux/slab.h>
13 #include <linux/pci.h>
14 #include <linux/hwmon.h>
15 #include <linux/err.h>
16 #include <linux/mutex.h>
17 #include <asm/processor.h>
18 #include <asm/cpuid/api.h>
19 
20 #define TEMP_FROM_REG(val)	(((((val) >> 16) & 0xff) - 49) * 1000)
21 #define REG_TEMP	0xe4
22 #define SEL_PLACE	0x40
23 #define SEL_CORE	0x04
24 
25 struct k8temp_data {
26 	struct mutex update_lock;
27 
28 	/* registers values */
29 	u8 sensorsp;		/* sensor presence bits - SEL_CORE, SEL_PLACE */
30 	u8 swap_core_select;    /* meaning of SEL_CORE is inverted */
31 	u32 temp_offset;
32 };
33 
34 static const struct pci_device_id k8temp_ids[] = {
35 	{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_K8_NB_MISC) },
36 	{ 0 },
37 };
38 MODULE_DEVICE_TABLE(pci, k8temp_ids);
39 
40 static int is_rev_g_desktop(u8 model)
41 {
42 	u32 brandidx;
43 
44 	if (model < 0x69)
45 		return 0;
46 
47 	if (model == 0xc1 || model == 0x6c || model == 0x7c)
48 		return 0;
49 
50 	/*
51 	 * Differentiate between AM2 and ASB1.
52 	 * See "Constructing the processor Name String" in "Revision
53 	 * Guide for AMD NPT Family 0Fh Processors" (33610).
54 	 */
55 	brandidx = cpuid_ebx(0x80000001);
56 	brandidx = (brandidx >> 9) & 0x1f;
57 
58 	/* Single core */
59 	if ((model == 0x6f || model == 0x7f) &&
60 	    (brandidx == 0x7 || brandidx == 0x9 || brandidx == 0xc))
61 		return 0;
62 
63 	/* Dual core */
64 	if (model == 0x6b &&
65 	    (brandidx == 0xb || brandidx == 0xc))
66 		return 0;
67 
68 	return 1;
69 }
70 
71 static umode_t
72 k8temp_is_visible(const void *drvdata, enum hwmon_sensor_types type,
73 		  u32 attr, int channel)
74 {
75 	const struct k8temp_data *data = drvdata;
76 
77 	if ((channel & 1) && !(data->sensorsp & SEL_PLACE))
78 		return 0;
79 
80 	if ((channel & 2) && !(data->sensorsp & SEL_CORE))
81 		return 0;
82 
83 	return 0444;
84 }
85 
86 static int
87 k8temp_read(struct device *dev, enum hwmon_sensor_types type,
88 	    u32 attr, int channel, long *val)
89 {
90 	struct k8temp_data *data = dev_get_drvdata(dev);
91 	struct pci_dev *pdev = to_pci_dev(dev->parent);
92 	int core, place;
93 	u32 temp;
94 	u8 tmp;
95 
96 	core = (channel >> 1) & 1;
97 	place = channel & 1;
98 
99 	core ^= data->swap_core_select;
100 
101 	mutex_lock(&data->update_lock);
102 	pci_read_config_byte(pdev, REG_TEMP, &tmp);
103 	tmp &= ~(SEL_PLACE | SEL_CORE);
104 	if (core)
105 		tmp |= SEL_CORE;
106 	if (place)
107 		tmp |= SEL_PLACE;
108 	pci_write_config_byte(pdev, REG_TEMP, tmp);
109 	pci_read_config_dword(pdev, REG_TEMP, &temp);
110 	mutex_unlock(&data->update_lock);
111 
112 	*val = TEMP_FROM_REG(temp) + data->temp_offset;
113 
114 	return 0;
115 }
116 
117 static const struct hwmon_ops k8temp_ops = {
118 	.is_visible = k8temp_is_visible,
119 	.read = k8temp_read,
120 };
121 
122 static const struct hwmon_channel_info * const k8temp_info[] = {
123 	HWMON_CHANNEL_INFO(temp,
124 		HWMON_T_INPUT, HWMON_T_INPUT, HWMON_T_INPUT, HWMON_T_INPUT),
125 	NULL
126 };
127 
128 static const struct hwmon_chip_info k8temp_chip_info = {
129 	.ops = &k8temp_ops,
130 	.info = k8temp_info,
131 };
132 
133 static int k8temp_probe(struct pci_dev *pdev,
134 				  const struct pci_device_id *id)
135 {
136 	u8 scfg;
137 	u32 temp;
138 	u8 model, stepping;
139 	struct k8temp_data *data;
140 	struct device *hwmon_dev;
141 
142 	data = devm_kzalloc(&pdev->dev, sizeof(struct k8temp_data), GFP_KERNEL);
143 	if (!data)
144 		return -ENOMEM;
145 
146 	model = boot_cpu_data.x86_model;
147 	stepping = boot_cpu_data.x86_stepping;
148 
149 	/* feature available since SH-C0, exclude older revisions */
150 	if ((model == 4 && stepping == 0) ||
151 	    (model == 5 && stepping <= 1))
152 		return -ENODEV;
153 
154 	/*
155 	 * AMD NPT family 0fh, i.e. RevF and RevG:
156 	 * meaning of SEL_CORE bit is inverted
157 	 */
158 	if (model >= 0x40) {
159 		data->swap_core_select = 1;
160 		dev_warn(&pdev->dev,
161 			 "Temperature readouts might be wrong - check erratum #141\n");
162 	}
163 
164 	/*
165 	 * RevG desktop CPUs (i.e. no socket S1G1 or ASB1 parts) need
166 	 * additional offset, otherwise reported temperature is below
167 	 * ambient temperature
168 	 */
169 	if (is_rev_g_desktop(model))
170 		data->temp_offset = 21000;
171 
172 	pci_read_config_byte(pdev, REG_TEMP, &scfg);
173 	scfg &= ~(SEL_PLACE | SEL_CORE);	/* Select sensor 0, core0 */
174 	pci_write_config_byte(pdev, REG_TEMP, scfg);
175 	pci_read_config_byte(pdev, REG_TEMP, &scfg);
176 
177 	if (scfg & (SEL_PLACE | SEL_CORE)) {
178 		dev_err(&pdev->dev, "Configuration bit(s) stuck at 1!\n");
179 		return -ENODEV;
180 	}
181 
182 	scfg |= (SEL_PLACE | SEL_CORE);
183 	pci_write_config_byte(pdev, REG_TEMP, scfg);
184 
185 	/* now we know if we can change core and/or sensor */
186 	pci_read_config_byte(pdev, REG_TEMP, &data->sensorsp);
187 
188 	if (data->sensorsp & SEL_PLACE) {
189 		scfg &= ~SEL_CORE;	/* Select sensor 1, core0 */
190 		pci_write_config_byte(pdev, REG_TEMP, scfg);
191 		pci_read_config_dword(pdev, REG_TEMP, &temp);
192 		scfg |= SEL_CORE;	/* prepare for next selection */
193 		if (!((temp >> 16) & 0xff)) /* if temp is 0 -49C is unlikely */
194 			data->sensorsp &= ~SEL_PLACE;
195 	}
196 
197 	if (data->sensorsp & SEL_CORE) {
198 		scfg &= ~SEL_PLACE;	/* Select sensor 0, core1 */
199 		pci_write_config_byte(pdev, REG_TEMP, scfg);
200 		pci_read_config_dword(pdev, REG_TEMP, &temp);
201 		if (!((temp >> 16) & 0xff)) /* if temp is 0 -49C is unlikely */
202 			data->sensorsp &= ~SEL_CORE;
203 	}
204 
205 	mutex_init(&data->update_lock);
206 
207 	hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
208 							 "k8temp",
209 							 data,
210 							 &k8temp_chip_info,
211 							 NULL);
212 
213 	return PTR_ERR_OR_ZERO(hwmon_dev);
214 }
215 
216 static struct pci_driver k8temp_driver = {
217 	.name = "k8temp",
218 	.id_table = k8temp_ids,
219 	.probe = k8temp_probe,
220 };
221 
222 module_pci_driver(k8temp_driver);
223 
224 MODULE_AUTHOR("Rudolf Marek <r.marek@assembler.cz>");
225 MODULE_DESCRIPTION("AMD K8 core temperature monitor");
226 MODULE_LICENSE("GPL");
227