aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRudolf Marek <r.marek@sh.cvut.cz>2006-08-28 08:40:17 -0400
committerGreg Kroah-Hartman <gregkh@suse.de>2006-09-28 18:31:14 -0400
commit29fa06c1292f473ae51a84f55c8fe22179bc1080 (patch)
tree81f21d0692217fb4829452ec6f37e71574208ad6
parentb19367c6f438b3a7700aceca21a03396702069ce (diff)
hwmon: New driver k8temp
Add support for the temperature sensor(s) found in AMD K8 CPUs. Signed-off-by: Rudolf Marek <r.marek@sh.cvut.cz> Signed-off-by: Jean Delvare <khali@linux-fr.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
-rw-r--r--drivers/hwmon/Kconfig10
-rw-r--r--drivers/hwmon/Makefile1
-rw-r--r--drivers/hwmon/k8temp.c292
-rw-r--r--include/linux/pci_ids.h1
4 files changed, 304 insertions, 0 deletions
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 78c237f8fa0a..d9f86e9d405b 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -94,6 +94,16 @@ config SENSORS_ADM9240
94 This driver can also be built as a module. If so, the module 94 This driver can also be built as a module. If so, the module
95 will be called adm9240. 95 will be called adm9240.
96 96
97config SENSORS_K8TEMP
98 tristate "AMD K8 processor sensor"
99 depends on HWMON && X86 && PCI && EXPERIMENTAL
100 help
101 If you say yes here you get support for the temperature
102 sensor(s) inside your AMD K8 CPU.
103
104 This driver can also be built as a module. If so, the module
105 will be called k8temp.
106
97config SENSORS_ASB100 107config SENSORS_ASB100
98 tristate "Asus ASB100 Bach" 108 tristate "Asus ASB100 Bach"
99 depends on HWMON && I2C && EXPERIMENTAL 109 depends on HWMON && I2C && EXPERIMENTAL
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 31415843a91a..aab4c1063059 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o
27obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o 27obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o
28obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o 28obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o
29obj-$(CONFIG_SENSORS_IT87) += it87.o 29obj-$(CONFIG_SENSORS_IT87) += it87.o
30obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o
30obj-$(CONFIG_SENSORS_LM63) += lm63.o 31obj-$(CONFIG_SENSORS_LM63) += lm63.o
31obj-$(CONFIG_SENSORS_LM70) += lm70.o 32obj-$(CONFIG_SENSORS_LM70) += lm70.o
32obj-$(CONFIG_SENSORS_LM75) += lm75.o 33obj-$(CONFIG_SENSORS_LM75) += lm75.o
diff --git a/drivers/hwmon/k8temp.c b/drivers/hwmon/k8temp.c
new file mode 100644
index 000000000000..50162ffa8832
--- /dev/null
+++ b/drivers/hwmon/k8temp.c
@@ -0,0 +1,292 @@
1/*
2 * k8temp.c - Linux kernel module for hardware monitoring
3 *
4 * Copyright (C) 2006 Rudolf Marek <r.marek@sh.cvut.cz>
5 *
6 * Inspired from the w83785 and amd756 drivers.
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 * 02110-1301 USA.
22 */
23
24#include <linux/module.h>
25#include <linux/delay.h>
26#include <linux/init.h>
27#include <linux/slab.h>
28#include <linux/jiffies.h>
29#include <linux/pci.h>
30#include <linux/hwmon.h>
31#include <linux/hwmon-sysfs.h>
32#include <linux/err.h>
33#include <linux/mutex.h>
34
35#define TEMP_FROM_REG(val) (((((val) >> 16) & 0xff) - 49) * 1000)
36#define REG_TEMP 0xe4
37#define SEL_PLACE 0x40
38#define SEL_CORE 0x04
39
40struct k8temp_data {
41 struct class_device *class_dev;
42 struct mutex update_lock;
43 const char *name;
44 char valid; /* zero until following fields are valid */
45 unsigned long last_updated; /* in jiffies */
46
47 /* registers values */
48 u8 sensorsp; /* sensor presence bits - SEL_CORE & SEL_PLACE */
49 u32 temp[2][2]; /* core, place */
50};
51
52static struct k8temp_data *k8temp_update_device(struct device *dev)
53{
54 struct k8temp_data *data = dev_get_drvdata(dev);
55 struct pci_dev *pdev = to_pci_dev(dev);
56 u8 tmp;
57
58 mutex_lock(&data->update_lock);
59
60 if (!data->valid
61 || time_after(jiffies, data->last_updated + HZ)) {
62 pci_read_config_byte(pdev, REG_TEMP, &tmp);
63 tmp &= ~(SEL_PLACE | SEL_CORE); /* Select sensor 0, core0 */
64 pci_write_config_byte(pdev, REG_TEMP, tmp);
65 pci_read_config_dword(pdev, REG_TEMP, &data->temp[0][0]);
66
67 if (data->sensorsp & SEL_PLACE) {
68 tmp |= SEL_PLACE; /* Select sensor 1, core0 */
69 pci_write_config_byte(pdev, REG_TEMP, tmp);
70 pci_read_config_dword(pdev, REG_TEMP,
71 &data->temp[0][1]);
72 }
73
74 if (data->sensorsp & SEL_CORE) {
75 tmp &= ~SEL_PLACE; /* Select sensor 0, core1 */
76 tmp |= SEL_CORE;
77 pci_write_config_byte(pdev, REG_TEMP, tmp);
78 pci_read_config_dword(pdev, REG_TEMP,
79 &data->temp[1][0]);
80
81 if (data->sensorsp & SEL_PLACE) {
82 tmp |= SEL_PLACE; /* Select sensor 1, core1 */
83 pci_write_config_byte(pdev, REG_TEMP, tmp);
84 pci_read_config_dword(pdev, REG_TEMP,
85 &data->temp[1][1]);
86 }
87 }
88
89 data->last_updated = jiffies;
90 data->valid = 1;
91 }
92
93 mutex_unlock(&data->update_lock);
94 return data;
95}
96
97/*
98 * Sysfs stuff
99 */
100
101static ssize_t show_name(struct device *dev, struct device_attribute
102 *devattr, char *buf)
103{
104 struct k8temp_data *data = dev_get_drvdata(dev);
105
106 return sprintf(buf, "%s\n", data->name);
107}
108
109
110static ssize_t show_temp(struct device *dev,
111 struct device_attribute *devattr, char *buf)
112{
113 struct sensor_device_attribute_2 *attr =
114 to_sensor_dev_attr_2(devattr);
115 int core = attr->nr;
116 int place = attr->index;
117 struct k8temp_data *data = k8temp_update_device(dev);
118
119 return sprintf(buf, "%d\n",
120 TEMP_FROM_REG(data->temp[core][place]));
121}
122
123/* core, place */
124
125static SENSOR_DEVICE_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, 0, 0);
126static SENSOR_DEVICE_ATTR_2(temp2_input, S_IRUGO, show_temp, NULL, 0, 1);
127static SENSOR_DEVICE_ATTR_2(temp3_input, S_IRUGO, show_temp, NULL, 1, 0);
128static SENSOR_DEVICE_ATTR_2(temp4_input, S_IRUGO, show_temp, NULL, 1, 1);
129static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
130
131static struct pci_device_id k8temp_ids[] = {
132 { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_K8_NB_MISC) },
133 { 0 },
134};
135
136static int __devinit k8temp_probe(struct pci_dev *pdev,
137 const struct pci_device_id *id)
138{
139 int err;
140 u8 scfg;
141 u32 temp;
142 struct k8temp_data *data;
143 u32 cpuid = cpuid_eax(1);
144
145 /* this feature should be available since SH-C0 core */
146 if ((cpuid == 0xf40) || (cpuid == 0xf50) || (cpuid == 0xf51)) {
147 err = -ENODEV;
148 goto exit;
149 }
150
151 if (!(data = kzalloc(sizeof(struct k8temp_data), GFP_KERNEL))) {
152 err = -ENOMEM;
153 goto exit;
154 }
155
156 pci_read_config_byte(pdev, REG_TEMP, &scfg);
157 scfg &= ~(SEL_PLACE | SEL_CORE); /* Select sensor 0, core0 */
158 pci_write_config_byte(pdev, REG_TEMP, scfg);
159 pci_read_config_byte(pdev, REG_TEMP, &scfg);
160
161 if (scfg & (SEL_PLACE | SEL_CORE)) {
162 dev_err(&pdev->dev, "Configuration bit(s) stuck at 1!\n");
163 err = -ENODEV;
164 goto exit_free;
165 }
166
167 scfg |= (SEL_PLACE | SEL_CORE);
168 pci_write_config_byte(pdev, REG_TEMP, scfg);
169
170 /* now we know if we can change core and/or sensor */
171 pci_read_config_byte(pdev, REG_TEMP, &data->sensorsp);
172
173 if (data->sensorsp & SEL_PLACE) {
174 scfg &= ~SEL_CORE; /* Select sensor 1, core0 */
175 pci_write_config_byte(pdev, REG_TEMP, scfg);
176 pci_read_config_dword(pdev, REG_TEMP, &temp);
177 scfg |= SEL_CORE; /* prepare for next selection */
178 if (!((temp >> 16) & 0xff)) /* if temp is 0 -49C is not likely */
179 data->sensorsp &= ~SEL_PLACE;
180 }
181
182 if (data->sensorsp & SEL_CORE) {
183 scfg &= ~SEL_PLACE; /* Select sensor 0, core1 */
184 pci_write_config_byte(pdev, REG_TEMP, scfg);
185 pci_read_config_dword(pdev, REG_TEMP, &temp);
186 if (!((temp >> 16) & 0xff)) /* if temp is 0 -49C is not likely */
187 data->sensorsp &= ~SEL_CORE;
188 }
189
190 data->name = "k8temp";
191 mutex_init(&data->update_lock);
192 dev_set_drvdata(&pdev->dev, data);
193
194 /* Register sysfs hooks */
195 err = device_create_file(&pdev->dev,
196 &sensor_dev_attr_temp1_input.dev_attr);
197 if (err)
198 goto exit_remove;
199
200 /* sensor can be changed and reports something */
201 if (data->sensorsp & SEL_PLACE) {
202 err = device_create_file(&pdev->dev,
203 &sensor_dev_attr_temp2_input.dev_attr);
204 if (err)
205 goto exit_remove;
206 }
207
208 /* core can be changed and reports something */
209 if (data->sensorsp & SEL_CORE) {
210 err = device_create_file(&pdev->dev,
211 &sensor_dev_attr_temp3_input.dev_attr);
212 if (err)
213 goto exit_remove;
214 if (data->sensorsp & SEL_PLACE)
215 err = device_create_file(&pdev->dev,
216 &sensor_dev_attr_temp4_input.
217 dev_attr);
218 if (err)
219 goto exit_remove;
220 }
221
222 err = device_create_file(&pdev->dev, &dev_attr_name);
223 if (err)
224 goto exit_remove;
225
226 data->class_dev = hwmon_device_register(&pdev->dev);
227
228 if (IS_ERR(data->class_dev)) {
229 err = PTR_ERR(data->class_dev);
230 goto exit_remove;
231 }
232
233 return 0;
234
235exit_remove:
236 device_remove_file(&pdev->dev,
237 &sensor_dev_attr_temp1_input.dev_attr);
238 device_remove_file(&pdev->dev,
239 &sensor_dev_attr_temp2_input.dev_attr);
240 device_remove_file(&pdev->dev,
241 &sensor_dev_attr_temp3_input.dev_attr);
242 device_remove_file(&pdev->dev,
243 &sensor_dev_attr_temp4_input.dev_attr);
244 device_remove_file(&pdev->dev, &dev_attr_name);
245exit_free:
246 dev_set_drvdata(&pdev->dev, NULL);
247 kfree(data);
248exit:
249 return err;
250}
251
252static void __devexit k8temp_remove(struct pci_dev *pdev)
253{
254 struct k8temp_data *data = dev_get_drvdata(&pdev->dev);
255
256 hwmon_device_unregister(data->class_dev);
257 device_remove_file(&pdev->dev,
258 &sensor_dev_attr_temp1_input.dev_attr);
259 device_remove_file(&pdev->dev,
260 &sensor_dev_attr_temp2_input.dev_attr);
261 device_remove_file(&pdev->dev,
262 &sensor_dev_attr_temp3_input.dev_attr);
263 device_remove_file(&pdev->dev,
264 &sensor_dev_attr_temp4_input.dev_attr);
265 device_remove_file(&pdev->dev, &dev_attr_name);
266 dev_set_drvdata(&pdev->dev, NULL);
267 kfree(data);
268}
269
270static struct pci_driver k8temp_driver = {
271 .name = "k8temp",
272 .id_table = k8temp_ids,
273 .probe = k8temp_probe,
274 .remove = __devexit_p(k8temp_remove),
275};
276
277static int __init k8temp_init(void)
278{
279 return pci_register_driver(&k8temp_driver);
280}
281
282static void __exit k8temp_exit(void)
283{
284 pci_unregister_driver(&k8temp_driver);
285}
286
287MODULE_AUTHOR("Rudolf Marek <r.marek@sh.cvut.cz>");
288MODULE_DESCRIPTION("AMD K8 core temperature monitor");
289MODULE_LICENSE("GPL");
290
291module_init(k8temp_init)
292module_exit(k8temp_exit)
diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h
index ab032ceafa84..61db1907f06f 100644
--- a/include/linux/pci_ids.h
+++ b/include/linux/pci_ids.h
@@ -479,6 +479,7 @@
479 479
480#define PCI_VENDOR_ID_AMD 0x1022 480#define PCI_VENDOR_ID_AMD 0x1022
481#define PCI_DEVICE_ID_AMD_K8_NB 0x1100 481#define PCI_DEVICE_ID_AMD_K8_NB 0x1100
482#define PCI_DEVICE_ID_AMD_K8_NB_MISC 0x1103
482#define PCI_DEVICE_ID_AMD_LANCE 0x2000 483#define PCI_DEVICE_ID_AMD_LANCE 0x2000
483#define PCI_DEVICE_ID_AMD_LANCE_HOME 0x2001 484#define PCI_DEVICE_ID_AMD_LANCE_HOME 0x2001
484#define PCI_DEVICE_ID_AMD_SCSI 0x2020 485#define PCI_DEVICE_ID_AMD_SCSI 0x2020