170c38772aef27b01dc236fb4016261c3828df6aaHarald Welte/*
270c38772aef27b01dc236fb4016261c3828df6aaHarald Welte * via-cputemp.c - Driver for VIA CPU core temperature monitoring
370c38772aef27b01dc236fb4016261c3828df6aaHarald Welte * Copyright (C) 2009 VIA Technologies, Inc.
470c38772aef27b01dc236fb4016261c3828df6aaHarald Welte *
570c38772aef27b01dc236fb4016261c3828df6aaHarald Welte * based on existing coretemp.c, which is
670c38772aef27b01dc236fb4016261c3828df6aaHarald Welte *
770c38772aef27b01dc236fb4016261c3828df6aaHarald Welte * Copyright (C) 2007 Rudolf Marek <r.marek@assembler.cz>
870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte *
970c38772aef27b01dc236fb4016261c3828df6aaHarald Welte * This program is free software; you can redistribute it and/or modify
1070c38772aef27b01dc236fb4016261c3828df6aaHarald Welte * it under the terms of the GNU General Public License as published by
1170c38772aef27b01dc236fb4016261c3828df6aaHarald Welte * the Free Software Foundation; version 2 of the License.
1270c38772aef27b01dc236fb4016261c3828df6aaHarald Welte *
1370c38772aef27b01dc236fb4016261c3828df6aaHarald Welte * This program is distributed in the hope that it will be useful,
1470c38772aef27b01dc236fb4016261c3828df6aaHarald Welte * but WITHOUT ANY WARRANTY; without even the implied warranty of
1570c38772aef27b01dc236fb4016261c3828df6aaHarald Welte * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1670c38772aef27b01dc236fb4016261c3828df6aaHarald Welte * GNU General Public License for more details.
1770c38772aef27b01dc236fb4016261c3828df6aaHarald Welte *
1870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte * You should have received a copy of the GNU General Public License
1970c38772aef27b01dc236fb4016261c3828df6aaHarald Welte * along with this program; if not, write to the Free Software
2070c38772aef27b01dc236fb4016261c3828df6aaHarald Welte * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
2170c38772aef27b01dc236fb4016261c3828df6aaHarald Welte * 02110-1301 USA.
2270c38772aef27b01dc236fb4016261c3828df6aaHarald Welte */
2370c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
24edb8d53c684da4062ac8c461727ddc1839336aa5Joe Perches#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
25edb8d53c684da4062ac8c461727ddc1839336aa5Joe Perches
2670c38772aef27b01dc236fb4016261c3828df6aaHarald Welte#include <linux/module.h>
2770c38772aef27b01dc236fb4016261c3828df6aaHarald Welte#include <linux/init.h>
2870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte#include <linux/slab.h>
2970c38772aef27b01dc236fb4016261c3828df6aaHarald Welte#include <linux/hwmon.h>
30764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare#include <linux/hwmon-vid.h>
3170c38772aef27b01dc236fb4016261c3828df6aaHarald Welte#include <linux/sysfs.h>
3270c38772aef27b01dc236fb4016261c3828df6aaHarald Welte#include <linux/hwmon-sysfs.h>
3370c38772aef27b01dc236fb4016261c3828df6aaHarald Welte#include <linux/err.h>
3470c38772aef27b01dc236fb4016261c3828df6aaHarald Welte#include <linux/mutex.h>
3570c38772aef27b01dc236fb4016261c3828df6aaHarald Welte#include <linux/list.h>
3670c38772aef27b01dc236fb4016261c3828df6aaHarald Welte#include <linux/platform_device.h>
3770c38772aef27b01dc236fb4016261c3828df6aaHarald Welte#include <linux/cpu.h>
3870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte#include <asm/msr.h>
3970c38772aef27b01dc236fb4016261c3828df6aaHarald Welte#include <asm/processor.h>
4070c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
4170c38772aef27b01dc236fb4016261c3828df6aaHarald Welte#define DRVNAME	"via_cputemp"
4270c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
43f279941863f0d39fa8285b84449b52b8286a254bH. Peter Anvinenum { SHOW_TEMP, SHOW_LABEL, SHOW_NAME };
4470c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
4570c38772aef27b01dc236fb4016261c3828df6aaHarald Welte/*
4670c38772aef27b01dc236fb4016261c3828df6aaHarald Welte * Functions declaration
4770c38772aef27b01dc236fb4016261c3828df6aaHarald Welte */
4870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
4970c38772aef27b01dc236fb4016261c3828df6aaHarald Weltestruct via_cputemp_data {
5070c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	struct device *hwmon_dev;
5170c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	const char *name;
52764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare	u8 vrm;
5370c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	u32 id;
54764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare	u32 msr_temp;
55764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare	u32 msr_vid;
5670c38772aef27b01dc236fb4016261c3828df6aaHarald Welte};
5770c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
5870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte/*
5970c38772aef27b01dc236fb4016261c3828df6aaHarald Welte * Sysfs stuff
6070c38772aef27b01dc236fb4016261c3828df6aaHarald Welte */
6170c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
6270c38772aef27b01dc236fb4016261c3828df6aaHarald Weltestatic ssize_t show_name(struct device *dev, struct device_attribute
6370c38772aef27b01dc236fb4016261c3828df6aaHarald Welte			  *devattr, char *buf)
6470c38772aef27b01dc236fb4016261c3828df6aaHarald Welte{
6570c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	int ret;
6670c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
6770c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	struct via_cputemp_data *data = dev_get_drvdata(dev);
6870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
6970c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	if (attr->index == SHOW_NAME)
7070c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		ret = sprintf(buf, "%s\n", data->name);
7170c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	else	/* show label */
7270c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		ret = sprintf(buf, "Core %d\n", data->id);
7370c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	return ret;
7470c38772aef27b01dc236fb4016261c3828df6aaHarald Welte}
7570c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
7670c38772aef27b01dc236fb4016261c3828df6aaHarald Weltestatic ssize_t show_temp(struct device *dev,
7770c38772aef27b01dc236fb4016261c3828df6aaHarald Welte			 struct device_attribute *devattr, char *buf)
7870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte{
7970c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	struct via_cputemp_data *data = dev_get_drvdata(dev);
8070c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	u32 eax, edx;
8170c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	int err;
8270c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
83764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare	err = rdmsr_safe_on_cpu(data->id, data->msr_temp, &eax, &edx);
8470c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	if (err)
8570c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		return -EAGAIN;
8670c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
8770c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	return sprintf(buf, "%lu\n", ((unsigned long)eax & 0xffffff) * 1000);
8870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte}
8970c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
90764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvarestatic ssize_t show_cpu_vid(struct device *dev,
91764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare			    struct device_attribute *devattr, char *buf)
92764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare{
93764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare	struct via_cputemp_data *data = dev_get_drvdata(dev);
94764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare	u32 eax, edx;
95764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare	int err;
96764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare
97764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare	err = rdmsr_safe_on_cpu(data->id, data->msr_vid, &eax, &edx);
98764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare	if (err)
99764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare		return -EAGAIN;
100764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare
101764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare	return sprintf(buf, "%d\n", vid_from_reg(~edx & 0x7f, data->vrm));
102764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare}
103764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare
10470c38772aef27b01dc236fb4016261c3828df6aaHarald Weltestatic SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL,
10570c38772aef27b01dc236fb4016261c3828df6aaHarald Welte			  SHOW_TEMP);
10670c38772aef27b01dc236fb4016261c3828df6aaHarald Weltestatic SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_name, NULL, SHOW_LABEL);
10770c38772aef27b01dc236fb4016261c3828df6aaHarald Weltestatic SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, SHOW_NAME);
10870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
10970c38772aef27b01dc236fb4016261c3828df6aaHarald Weltestatic struct attribute *via_cputemp_attributes[] = {
11070c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	&sensor_dev_attr_name.dev_attr.attr,
11170c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	&sensor_dev_attr_temp1_label.dev_attr.attr,
11270c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	&sensor_dev_attr_temp1_input.dev_attr.attr,
11370c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	NULL
11470c38772aef27b01dc236fb4016261c3828df6aaHarald Welte};
11570c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
11670c38772aef27b01dc236fb4016261c3828df6aaHarald Weltestatic const struct attribute_group via_cputemp_group = {
11770c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	.attrs = via_cputemp_attributes,
11870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte};
11970c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
120764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare/* Optional attributes */
121764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvarestatic DEVICE_ATTR(cpu0_vid, S_IRUGO, show_cpu_vid, NULL);
122764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare
12370c38772aef27b01dc236fb4016261c3828df6aaHarald Weltestatic int __devinit via_cputemp_probe(struct platform_device *pdev)
12470c38772aef27b01dc236fb4016261c3828df6aaHarald Welte{
12570c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	struct via_cputemp_data *data;
12670c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	struct cpuinfo_x86 *c = &cpu_data(pdev->id);
12770c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	int err;
12870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	u32 eax, edx;
12970c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
13070c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	data = kzalloc(sizeof(struct via_cputemp_data), GFP_KERNEL);
13170c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	if (!data) {
13270c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		err = -ENOMEM;
13370c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		dev_err(&pdev->dev, "Out of memory\n");
13470c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		goto exit;
13570c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	}
13670c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
13770c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	data->id = pdev->id;
13870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	data->name = "via_cputemp";
13970c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
14070c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	switch (c->x86_model) {
14170c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	case 0xA:
14270c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		/* C7 A */
14370c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	case 0xD:
14470c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		/* C7 D */
145764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare		data->msr_temp = 0x1169;
146764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare		data->msr_vid = 0x198;
14770c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		break;
14870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	case 0xF:
14970c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		/* Nano */
150764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare		data->msr_temp = 0x1423;
15170c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		break;
15270c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	default:
15370c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		err = -ENODEV;
15470c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		goto exit_free;
15570c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	}
15670c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
15770c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	/* test if we can access the TEMPERATURE MSR */
158764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare	err = rdmsr_safe_on_cpu(data->id, data->msr_temp, &eax, &edx);
15970c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	if (err) {
16070c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		dev_err(&pdev->dev,
16170c38772aef27b01dc236fb4016261c3828df6aaHarald Welte			"Unable to access TEMPERATURE MSR, giving up\n");
16270c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		goto exit_free;
16370c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	}
16470c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
16570c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	platform_set_drvdata(pdev, data);
16670c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
16770c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	err = sysfs_create_group(&pdev->dev.kobj, &via_cputemp_group);
16870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	if (err)
16970c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		goto exit_free;
17070c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
171764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare	if (data->msr_vid)
172764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare		data->vrm = vid_which_vrm();
173764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare
174764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare	if (data->vrm) {
175764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare		err = device_create_file(&pdev->dev, &dev_attr_cpu0_vid);
176764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare		if (err)
177764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare			goto exit_remove;
178764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare	}
179764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare
18070c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	data->hwmon_dev = hwmon_device_register(&pdev->dev);
18170c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	if (IS_ERR(data->hwmon_dev)) {
18270c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		err = PTR_ERR(data->hwmon_dev);
18370c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		dev_err(&pdev->dev, "Class registration failed (%d)\n",
18470c38772aef27b01dc236fb4016261c3828df6aaHarald Welte			err);
18570c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		goto exit_remove;
18670c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	}
18770c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
18870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	return 0;
18970c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
19070c38772aef27b01dc236fb4016261c3828df6aaHarald Welteexit_remove:
191764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare	if (data->vrm)
192764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare		device_remove_file(&pdev->dev, &dev_attr_cpu0_vid);
19370c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	sysfs_remove_group(&pdev->dev.kobj, &via_cputemp_group);
19470c38772aef27b01dc236fb4016261c3828df6aaHarald Welteexit_free:
19570c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	platform_set_drvdata(pdev, NULL);
19670c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	kfree(data);
19770c38772aef27b01dc236fb4016261c3828df6aaHarald Welteexit:
19870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	return err;
19970c38772aef27b01dc236fb4016261c3828df6aaHarald Welte}
20070c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
20170c38772aef27b01dc236fb4016261c3828df6aaHarald Weltestatic int __devexit via_cputemp_remove(struct platform_device *pdev)
20270c38772aef27b01dc236fb4016261c3828df6aaHarald Welte{
20370c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	struct via_cputemp_data *data = platform_get_drvdata(pdev);
20470c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
20570c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	hwmon_device_unregister(data->hwmon_dev);
206764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare	if (data->vrm)
207764e043bb48b6b94f9dec228aedbd8ab08f4708bJean Delvare		device_remove_file(&pdev->dev, &dev_attr_cpu0_vid);
20870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	sysfs_remove_group(&pdev->dev.kobj, &via_cputemp_group);
20970c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	platform_set_drvdata(pdev, NULL);
21070c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	kfree(data);
21170c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	return 0;
21270c38772aef27b01dc236fb4016261c3828df6aaHarald Welte}
21370c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
21470c38772aef27b01dc236fb4016261c3828df6aaHarald Weltestatic struct platform_driver via_cputemp_driver = {
21570c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	.driver = {
21670c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		.owner = THIS_MODULE,
21770c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		.name = DRVNAME,
21870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	},
21970c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	.probe = via_cputemp_probe,
22070c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	.remove = __devexit_p(via_cputemp_remove),
22170c38772aef27b01dc236fb4016261c3828df6aaHarald Welte};
22270c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
22370c38772aef27b01dc236fb4016261c3828df6aaHarald Weltestruct pdev_entry {
22470c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	struct list_head list;
22570c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	struct platform_device *pdev;
22670c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	unsigned int cpu;
22770c38772aef27b01dc236fb4016261c3828df6aaHarald Welte};
22870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
22970c38772aef27b01dc236fb4016261c3828df6aaHarald Weltestatic LIST_HEAD(pdev_list);
23070c38772aef27b01dc236fb4016261c3828df6aaHarald Weltestatic DEFINE_MUTEX(pdev_list_mutex);
23170c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
23270c38772aef27b01dc236fb4016261c3828df6aaHarald Weltestatic int __cpuinit via_cputemp_device_add(unsigned int cpu)
23370c38772aef27b01dc236fb4016261c3828df6aaHarald Welte{
23470c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	int err;
23570c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	struct platform_device *pdev;
23670c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	struct pdev_entry *pdev_entry;
23770c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
23870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	pdev = platform_device_alloc(DRVNAME, cpu);
23970c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	if (!pdev) {
24070c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		err = -ENOMEM;
241edb8d53c684da4062ac8c461727ddc1839336aa5Joe Perches		pr_err("Device allocation failed\n");
24270c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		goto exit;
24370c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	}
24470c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
24570c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	pdev_entry = kzalloc(sizeof(struct pdev_entry), GFP_KERNEL);
24670c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	if (!pdev_entry) {
24770c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		err = -ENOMEM;
24870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		goto exit_device_put;
24970c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	}
25070c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
25170c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	err = platform_device_add(pdev);
25270c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	if (err) {
253edb8d53c684da4062ac8c461727ddc1839336aa5Joe Perches		pr_err("Device addition failed (%d)\n", err);
25470c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		goto exit_device_free;
25570c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	}
25670c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
25770c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	pdev_entry->pdev = pdev;
25870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	pdev_entry->cpu = cpu;
25970c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	mutex_lock(&pdev_list_mutex);
26070c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	list_add_tail(&pdev_entry->list, &pdev_list);
26170c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	mutex_unlock(&pdev_list_mutex);
26270c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
26370c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	return 0;
26470c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
26570c38772aef27b01dc236fb4016261c3828df6aaHarald Welteexit_device_free:
26670c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	kfree(pdev_entry);
26770c38772aef27b01dc236fb4016261c3828df6aaHarald Welteexit_device_put:
26870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	platform_device_put(pdev);
26970c38772aef27b01dc236fb4016261c3828df6aaHarald Welteexit:
27070c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	return err;
27170c38772aef27b01dc236fb4016261c3828df6aaHarald Welte}
27270c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
273a5f42a6bc51454137b918f67310168c27d1dd1deJan Beulichstatic void __cpuinit via_cputemp_device_remove(unsigned int cpu)
27470c38772aef27b01dc236fb4016261c3828df6aaHarald Welte{
275ae9e0ce73e9fc55a620e0b3bd4a154330a7e64ccJan Beulich	struct pdev_entry *p;
276ae9e0ce73e9fc55a620e0b3bd4a154330a7e64ccJan Beulich
27770c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	mutex_lock(&pdev_list_mutex);
278ae9e0ce73e9fc55a620e0b3bd4a154330a7e64ccJan Beulich	list_for_each_entry(p, &pdev_list, list) {
27970c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		if (p->cpu == cpu) {
28070c38772aef27b01dc236fb4016261c3828df6aaHarald Welte			platform_device_unregister(p->pdev);
28170c38772aef27b01dc236fb4016261c3828df6aaHarald Welte			list_del(&p->list);
282ae9e0ce73e9fc55a620e0b3bd4a154330a7e64ccJan Beulich			mutex_unlock(&pdev_list_mutex);
28370c38772aef27b01dc236fb4016261c3828df6aaHarald Welte			kfree(p);
284ae9e0ce73e9fc55a620e0b3bd4a154330a7e64ccJan Beulich			return;
28570c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		}
28670c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	}
28770c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	mutex_unlock(&pdev_list_mutex);
28870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte}
28970c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
29070c38772aef27b01dc236fb4016261c3828df6aaHarald Weltestatic int __cpuinit via_cputemp_cpu_callback(struct notifier_block *nfb,
29170c38772aef27b01dc236fb4016261c3828df6aaHarald Welte				 unsigned long action, void *hcpu)
29270c38772aef27b01dc236fb4016261c3828df6aaHarald Welte{
29370c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	unsigned int cpu = (unsigned long) hcpu;
29470c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
29570c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	switch (action) {
29670c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	case CPU_ONLINE:
29770c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	case CPU_DOWN_FAILED:
29870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		via_cputemp_device_add(cpu);
29970c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		break;
30070c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	case CPU_DOWN_PREPARE:
30170c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		via_cputemp_device_remove(cpu);
30270c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		break;
30370c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	}
30470c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	return NOTIFY_OK;
30570c38772aef27b01dc236fb4016261c3828df6aaHarald Welte}
30670c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
30770c38772aef27b01dc236fb4016261c3828df6aaHarald Weltestatic struct notifier_block via_cputemp_cpu_notifier __refdata = {
30870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	.notifier_call = via_cputemp_cpu_callback,
30970c38772aef27b01dc236fb4016261c3828df6aaHarald Welte};
31070c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
31170c38772aef27b01dc236fb4016261c3828df6aaHarald Weltestatic int __init via_cputemp_init(void)
31270c38772aef27b01dc236fb4016261c3828df6aaHarald Welte{
31370c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	int i, err;
31470c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
31570c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	if (cpu_data(0).x86_vendor != X86_VENDOR_CENTAUR) {
31670c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		printk(KERN_DEBUG DRVNAME ": Not a VIA CPU\n");
31770c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		err = -ENODEV;
31870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		goto exit;
31970c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	}
32070c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
32170c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	err = platform_driver_register(&via_cputemp_driver);
32270c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	if (err)
32370c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		goto exit;
32470c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
32570c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	for_each_online_cpu(i) {
32670c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		struct cpuinfo_x86 *c = &cpu_data(i);
32770c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
32870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		if (c->x86 != 6)
32970c38772aef27b01dc236fb4016261c3828df6aaHarald Welte			continue;
33070c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
33170c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		if (c->x86_model < 0x0a)
33270c38772aef27b01dc236fb4016261c3828df6aaHarald Welte			continue;
33370c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
33470c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		if (c->x86_model > 0x0f) {
335edb8d53c684da4062ac8c461727ddc1839336aa5Joe Perches			pr_warn("Unknown CPU model 0x%x\n", c->x86_model);
33670c38772aef27b01dc236fb4016261c3828df6aaHarald Welte			continue;
33770c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		}
33870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
339ae9e0ce73e9fc55a620e0b3bd4a154330a7e64ccJan Beulich		via_cputemp_device_add(i);
34070c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	}
341ae9e0ce73e9fc55a620e0b3bd4a154330a7e64ccJan Beulich
342ae9e0ce73e9fc55a620e0b3bd4a154330a7e64ccJan Beulich#ifndef CONFIG_HOTPLUG_CPU
34370c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	if (list_empty(&pdev_list)) {
34470c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		err = -ENODEV;
34570c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		goto exit_driver_unreg;
34670c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	}
347ae9e0ce73e9fc55a620e0b3bd4a154330a7e64ccJan Beulich#endif
34870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
34970c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	register_hotcpu_notifier(&via_cputemp_cpu_notifier);
35070c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	return 0;
35170c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
352ae9e0ce73e9fc55a620e0b3bd4a154330a7e64ccJan Beulich#ifndef CONFIG_HOTPLUG_CPU
35370c38772aef27b01dc236fb4016261c3828df6aaHarald Welteexit_driver_unreg:
35470c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	platform_driver_unregister(&via_cputemp_driver);
355ae9e0ce73e9fc55a620e0b3bd4a154330a7e64ccJan Beulich#endif
35670c38772aef27b01dc236fb4016261c3828df6aaHarald Welteexit:
35770c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	return err;
35870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte}
35970c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
36070c38772aef27b01dc236fb4016261c3828df6aaHarald Weltestatic void __exit via_cputemp_exit(void)
36170c38772aef27b01dc236fb4016261c3828df6aaHarald Welte{
36270c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	struct pdev_entry *p, *n;
36317c10d61c750619324ee2a46c5a9e03a435fe212Chen Gong
36470c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	unregister_hotcpu_notifier(&via_cputemp_cpu_notifier);
36570c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	mutex_lock(&pdev_list_mutex);
36670c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	list_for_each_entry_safe(p, n, &pdev_list, list) {
36770c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		platform_device_unregister(p->pdev);
36870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		list_del(&p->list);
36970c38772aef27b01dc236fb4016261c3828df6aaHarald Welte		kfree(p);
37070c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	}
37170c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	mutex_unlock(&pdev_list_mutex);
37270c38772aef27b01dc236fb4016261c3828df6aaHarald Welte	platform_driver_unregister(&via_cputemp_driver);
37370c38772aef27b01dc236fb4016261c3828df6aaHarald Welte}
37470c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
37570c38772aef27b01dc236fb4016261c3828df6aaHarald WelteMODULE_AUTHOR("Harald Welte <HaraldWelte@viatech.com>");
37670c38772aef27b01dc236fb4016261c3828df6aaHarald WelteMODULE_DESCRIPTION("VIA CPU temperature monitor");
37770c38772aef27b01dc236fb4016261c3828df6aaHarald WelteMODULE_LICENSE("GPL");
37870c38772aef27b01dc236fb4016261c3828df6aaHarald Welte
37970c38772aef27b01dc236fb4016261c3828df6aaHarald Weltemodule_init(via_cputemp_init)
38070c38772aef27b01dc236fb4016261c3828df6aaHarald Weltemodule_exit(via_cputemp_exit)
381