Blame view

drivers/hwmon/k8temp.c 9.36 KB
29fa06c12   Rudolf Marek   hwmon: New driver...
1
2
3
  /*
   * k8temp.c - Linux kernel module for hardware monitoring
   *
7188cc66b   Jean Delvare   hwmon: Update Rud...
4
   * Copyright (C) 2006 Rudolf Marek <r.marek@assembler.cz>
29fa06c12   Rudolf Marek   hwmon: New driver...
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
   *
   * Inspired from the w83785 and amd756 drivers.
   *
   * 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 <linux/module.h>
  #include <linux/delay.h>
  #include <linux/init.h>
  #include <linux/slab.h>
  #include <linux/jiffies.h>
  #include <linux/pci.h>
  #include <linux/hwmon.h>
  #include <linux/hwmon-sysfs.h>
  #include <linux/err.h>
  #include <linux/mutex.h>
bb9a35f29   Andreas Herrmann   hwmon: (k8temp) W...
34
  #include <asm/processor.h>
29fa06c12   Rudolf Marek   hwmon: New driver...
35
36
37
38
39
40
41
  
  #define TEMP_FROM_REG(val)	(((((val) >> 16) & 0xff) - 49) * 1000)
  #define REG_TEMP	0xe4
  #define SEL_PLACE	0x40
  #define SEL_CORE	0x04
  
  struct k8temp_data {
1beeffe43   Tony Jones   hwmon: Convert fr...
42
  	struct device *hwmon_dev;
29fa06c12   Rudolf Marek   hwmon: New driver...
43
44
45
46
47
48
49
50
  	struct mutex update_lock;
  	const char *name;
  	char valid;		/* zero until following fields are valid */
  	unsigned long last_updated;	/* in jiffies */
  
  	/* registers values */
  	u8 sensorsp;		/* sensor presence bits - SEL_CORE & SEL_PLACE */
  	u32 temp[2][2];		/* core, place */
a2e066bba   Andreas Herrmann   hwmon: (k8temp) F...
51
  	u8 swap_core_select;    /* meaning of SEL_CORE is inverted */
76ff08da3   Andreas Herrmann   hwmon: (k8temp) F...
52
  	u32 temp_offset;
29fa06c12   Rudolf Marek   hwmon: New driver...
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
  };
  
  static struct k8temp_data *k8temp_update_device(struct device *dev)
  {
  	struct k8temp_data *data = dev_get_drvdata(dev);
  	struct pci_dev *pdev = to_pci_dev(dev);
  	u8 tmp;
  
  	mutex_lock(&data->update_lock);
  
  	if (!data->valid
  	    || time_after(jiffies, data->last_updated + HZ)) {
  		pci_read_config_byte(pdev, REG_TEMP, &tmp);
  		tmp &= ~(SEL_PLACE | SEL_CORE);		/* Select sensor 0, core0 */
  		pci_write_config_byte(pdev, REG_TEMP, tmp);
  		pci_read_config_dword(pdev, REG_TEMP, &data->temp[0][0]);
  
  		if (data->sensorsp & SEL_PLACE) {
  			tmp |= SEL_PLACE;	/* Select sensor 1, core0 */
  			pci_write_config_byte(pdev, REG_TEMP, tmp);
  			pci_read_config_dword(pdev, REG_TEMP,
  					      &data->temp[0][1]);
  		}
  
  		if (data->sensorsp & SEL_CORE) {
  			tmp &= ~SEL_PLACE;	/* Select sensor 0, core1 */
  			tmp |= SEL_CORE;
  			pci_write_config_byte(pdev, REG_TEMP, tmp);
  			pci_read_config_dword(pdev, REG_TEMP,
  					      &data->temp[1][0]);
  
  			if (data->sensorsp & SEL_PLACE) {
  				tmp |= SEL_PLACE;	/* Select sensor 1, core1 */
  				pci_write_config_byte(pdev, REG_TEMP, tmp);
  				pci_read_config_dword(pdev, REG_TEMP,
  						      &data->temp[1][1]);
  			}
  		}
  
  		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 k8temp_data *data = dev_get_drvdata(dev);
  
  	return sprintf(buf, "%s
  ", data->name);
  }
  
  
  static ssize_t show_temp(struct device *dev,
  			 struct device_attribute *devattr, char *buf)
  {
  	struct sensor_device_attribute_2 *attr =
  	    to_sensor_dev_attr_2(devattr);
  	int core = attr->nr;
  	int place = attr->index;
76ff08da3   Andreas Herrmann   hwmon: (k8temp) F...
121
  	int temp;
29fa06c12   Rudolf Marek   hwmon: New driver...
122
  	struct k8temp_data *data = k8temp_update_device(dev);
cd4de21f7   Jean Delvare   hwmon: (k8temp) B...
123
  	if (data->swap_core_select && (data->sensorsp & SEL_CORE))
a2e066bba   Andreas Herrmann   hwmon: (k8temp) F...
124
  		core = core ? 0 : 1;
76ff08da3   Andreas Herrmann   hwmon: (k8temp) F...
125
126
127
128
  	temp = TEMP_FROM_REG(data->temp[core][place]) + data->temp_offset;
  
  	return sprintf(buf, "%d
  ", temp);
29fa06c12   Rudolf Marek   hwmon: New driver...
129
130
131
132
133
134
135
136
137
  }
  
  /* core, place */
  
  static SENSOR_DEVICE_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, 0, 0);
  static SENSOR_DEVICE_ATTR_2(temp2_input, S_IRUGO, show_temp, NULL, 0, 1);
  static SENSOR_DEVICE_ATTR_2(temp3_input, S_IRUGO, show_temp, NULL, 1, 0);
  static SENSOR_DEVICE_ATTR_2(temp4_input, S_IRUGO, show_temp, NULL, 1, 1);
  static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
3dd3a1563   Márton Németh   hwmon: Make PCI d...
138
  static const struct pci_device_id k8temp_ids[] = {
29fa06c12   Rudolf Marek   hwmon: New driver...
139
140
141
  	{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_K8_NB_MISC) },
  	{ 0 },
  };
b17ebc940   Jean Delvare   k8temp: Enable au...
142
  MODULE_DEVICE_TABLE(pci, k8temp_ids);
a05e93f3b   Andreas Herrmann   hwmon: (k8temp) D...
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
  static int __devinit is_rev_g_desktop(u8 model)
  {
  	u32 brandidx;
  
  	if (model < 0x69)
  		return 0;
  
  	if (model == 0xc1 || model == 0x6c || model == 0x7c)
  		return 0;
  
  	/*
  	 * Differentiate between AM2 and ASB1.
  	 * See "Constructing the processor Name String" in "Revision
  	 * Guide for AMD NPT Family 0Fh Processors" (33610).
  	 */
  	brandidx = cpuid_ebx(0x80000001);
  	brandidx = (brandidx >> 9) & 0x1f;
  
  	/* Single core */
  	if ((model == 0x6f || model == 0x7f) &&
  	    (brandidx == 0x7 || brandidx == 0x9 || brandidx == 0xc))
  		return 0;
  
  	/* Dual core */
  	if (model == 0x6b &&
  	    (brandidx == 0xb || brandidx == 0xc))
  		return 0;
  
  	return 1;
  }
29fa06c12   Rudolf Marek   hwmon: New driver...
173
174
175
176
177
178
  static int __devinit k8temp_probe(struct pci_dev *pdev,
  				  const struct pci_device_id *id)
  {
  	int err;
  	u8 scfg;
  	u32 temp;
bb9a35f29   Andreas Herrmann   hwmon: (k8temp) W...
179
  	u8 model, stepping;
29fa06c12   Rudolf Marek   hwmon: New driver...
180
  	struct k8temp_data *data;
29fa06c12   Rudolf Marek   hwmon: New driver...
181
182
183
184
185
  
  	if (!(data = kzalloc(sizeof(struct k8temp_data), GFP_KERNEL))) {
  		err = -ENOMEM;
  		goto exit;
  	}
bb9a35f29   Andreas Herrmann   hwmon: (k8temp) W...
186
187
  	model = boot_cpu_data.x86_model;
  	stepping = boot_cpu_data.x86_mask;
628b4504c   Andreas Herrmann   hwmon: (k8temp) R...
188
189
190
191
192
193
  	/* feature available since SH-C0, exclude older revisions */
  	if (((model == 4) && (stepping == 0)) ||
  	    ((model == 5) && (stepping <= 1))) {
  		err = -ENODEV;
  		goto exit_free;
  	}
76ff08da3   Andreas Herrmann   hwmon: (k8temp) F...
194

628b4504c   Andreas Herrmann   hwmon: (k8temp) R...
195
196
197
198
199
200
201
202
203
  	/*
  	 * AMD NPT family 0fh, i.e. RevF and RevG:
  	 * meaning of SEL_CORE bit is inverted
  	 */
  	if (model >= 0x40) {
  		data->swap_core_select = 1;
  		dev_warn(&pdev->dev, "Temperature readouts might be wrong - "
  			 "check erratum #141
  ");
bb9a35f29   Andreas Herrmann   hwmon: (k8temp) W...
204
  	}
628b4504c   Andreas Herrmann   hwmon: (k8temp) R...
205
206
207
208
209
210
211
  	/*
  	 * RevG desktop CPUs (i.e. no socket S1G1 or ASB1 parts) need
  	 * additional offset, otherwise reported temperature is below
  	 * ambient temperature
  	 */
  	if (is_rev_g_desktop(model))
  		data->temp_offset = 21000;
29fa06c12   Rudolf Marek   hwmon: New driver...
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
  	pci_read_config_byte(pdev, REG_TEMP, &scfg);
  	scfg &= ~(SEL_PLACE | SEL_CORE);		/* Select sensor 0, core0 */
  	pci_write_config_byte(pdev, REG_TEMP, scfg);
  	pci_read_config_byte(pdev, REG_TEMP, &scfg);
  
  	if (scfg & (SEL_PLACE | SEL_CORE)) {
  		dev_err(&pdev->dev, "Configuration bit(s) stuck at 1!
  ");
  		err = -ENODEV;
  		goto exit_free;
  	}
  
  	scfg |= (SEL_PLACE | SEL_CORE);
  	pci_write_config_byte(pdev, REG_TEMP, scfg);
  
  	/* now we know if we can change core and/or sensor */
  	pci_read_config_byte(pdev, REG_TEMP, &data->sensorsp);
  
  	if (data->sensorsp & SEL_PLACE) {
  		scfg &= ~SEL_CORE;	/* Select sensor 1, core0 */
  		pci_write_config_byte(pdev, REG_TEMP, scfg);
  		pci_read_config_dword(pdev, REG_TEMP, &temp);
  		scfg |= SEL_CORE;	/* prepare for next selection */
  		if (!((temp >> 16) & 0xff))	/* if temp is 0 -49C is not likely */
  			data->sensorsp &= ~SEL_PLACE;
  	}
  
  	if (data->sensorsp & SEL_CORE) {
  		scfg &= ~SEL_PLACE;	/* Select sensor 0, core1 */
  		pci_write_config_byte(pdev, REG_TEMP, scfg);
  		pci_read_config_dword(pdev, REG_TEMP, &temp);
  		if (!((temp >> 16) & 0xff))	/* if temp is 0 -49C is not likely */
  			data->sensorsp &= ~SEL_CORE;
  	}
  
  	data->name = "k8temp";
  	mutex_init(&data->update_lock);
95de3b257   Jean Delvare   hwmon: Use helper...
249
  	pci_set_drvdata(pdev, data);
29fa06c12   Rudolf Marek   hwmon: New driver...
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
  
  	/* Register sysfs hooks */
  	err = device_create_file(&pdev->dev,
  			   &sensor_dev_attr_temp1_input.dev_attr);
  	if (err)
  		goto exit_remove;
  
  	/* sensor can be changed and reports something */
  	if (data->sensorsp & SEL_PLACE) {
  		err = device_create_file(&pdev->dev,
  				   &sensor_dev_attr_temp2_input.dev_attr);
  		if (err)
  			goto exit_remove;
  	}
  
  	/* core can be changed and reports something */
  	if (data->sensorsp & SEL_CORE) {
  		err = device_create_file(&pdev->dev,
  				   &sensor_dev_attr_temp3_input.dev_attr);
  		if (err)
  			goto exit_remove;
df149d02e   Julia Lawall   hwmon: (k8temp) A...
271
  		if (data->sensorsp & SEL_PLACE) {
29fa06c12   Rudolf Marek   hwmon: New driver...
272
273
274
275
276
  			err = device_create_file(&pdev->dev,
  					   &sensor_dev_attr_temp4_input.
  					   dev_attr);
  			if (err)
  				goto exit_remove;
df149d02e   Julia Lawall   hwmon: (k8temp) A...
277
  		}
29fa06c12   Rudolf Marek   hwmon: New driver...
278
279
280
281
282
  	}
  
  	err = device_create_file(&pdev->dev, &dev_attr_name);
  	if (err)
  		goto exit_remove;
1beeffe43   Tony Jones   hwmon: Convert fr...
283
  	data->hwmon_dev = hwmon_device_register(&pdev->dev);
29fa06c12   Rudolf Marek   hwmon: New driver...
284

1beeffe43   Tony Jones   hwmon: Convert fr...
285
286
  	if (IS_ERR(data->hwmon_dev)) {
  		err = PTR_ERR(data->hwmon_dev);
29fa06c12   Rudolf Marek   hwmon: New driver...
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
  		goto exit_remove;
  	}
  
  	return 0;
  
  exit_remove:
  	device_remove_file(&pdev->dev,
  			   &sensor_dev_attr_temp1_input.dev_attr);
  	device_remove_file(&pdev->dev,
  			   &sensor_dev_attr_temp2_input.dev_attr);
  	device_remove_file(&pdev->dev,
  			   &sensor_dev_attr_temp3_input.dev_attr);
  	device_remove_file(&pdev->dev,
  			   &sensor_dev_attr_temp4_input.dev_attr);
  	device_remove_file(&pdev->dev, &dev_attr_name);
  exit_free:
95de3b257   Jean Delvare   hwmon: Use helper...
303
  	pci_set_drvdata(pdev, NULL);
29fa06c12   Rudolf Marek   hwmon: New driver...
304
305
306
307
308
309
310
  	kfree(data);
  exit:
  	return err;
  }
  
  static void __devexit k8temp_remove(struct pci_dev *pdev)
  {
95de3b257   Jean Delvare   hwmon: Use helper...
311
  	struct k8temp_data *data = pci_get_drvdata(pdev);
29fa06c12   Rudolf Marek   hwmon: New driver...
312

1beeffe43   Tony Jones   hwmon: Convert fr...
313
  	hwmon_device_unregister(data->hwmon_dev);
29fa06c12   Rudolf Marek   hwmon: New driver...
314
315
316
317
318
319
320
321
322
  	device_remove_file(&pdev->dev,
  			   &sensor_dev_attr_temp1_input.dev_attr);
  	device_remove_file(&pdev->dev,
  			   &sensor_dev_attr_temp2_input.dev_attr);
  	device_remove_file(&pdev->dev,
  			   &sensor_dev_attr_temp3_input.dev_attr);
  	device_remove_file(&pdev->dev,
  			   &sensor_dev_attr_temp4_input.dev_attr);
  	device_remove_file(&pdev->dev, &dev_attr_name);
95de3b257   Jean Delvare   hwmon: Use helper...
323
  	pci_set_drvdata(pdev, NULL);
29fa06c12   Rudolf Marek   hwmon: New driver...
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
  	kfree(data);
  }
  
  static struct pci_driver k8temp_driver = {
  	.name = "k8temp",
  	.id_table = k8temp_ids,
  	.probe = k8temp_probe,
  	.remove = __devexit_p(k8temp_remove),
  };
  
  static int __init k8temp_init(void)
  {
  	return pci_register_driver(&k8temp_driver);
  }
  
  static void __exit k8temp_exit(void)
  {
  	pci_unregister_driver(&k8temp_driver);
  }
7188cc66b   Jean Delvare   hwmon: Update Rud...
343
  MODULE_AUTHOR("Rudolf Marek <r.marek@assembler.cz>");
29fa06c12   Rudolf Marek   hwmon: New driver...
344
345
346
347
348
  MODULE_DESCRIPTION("AMD K8 core temperature monitor");
  MODULE_LICENSE("GPL");
  
  module_init(k8temp_init)
  module_exit(k8temp_exit)