329 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * via-cputemp.c - Driver for VIA CPU core temperature monitoring4 * Copyright (C) 2009 VIA Technologies, Inc.5 *6 * based on existing coretemp.c, which is7 *8 * Copyright (C) 2007 Rudolf Marek <r.marek@assembler.cz>9 */10 11#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt12 13#include <linux/module.h>14#include <linux/init.h>15#include <linux/slab.h>16#include <linux/hwmon.h>17#include <linux/hwmon-vid.h>18#include <linux/sysfs.h>19#include <linux/hwmon-sysfs.h>20#include <linux/err.h>21#include <linux/mutex.h>22#include <linux/list.h>23#include <linux/platform_device.h>24#include <linux/cpu.h>25#include <asm/msr.h>26#include <asm/processor.h>27#include <asm/cpu_device_id.h>28 29#define DRVNAME "via_cputemp"30 31enum { SHOW_TEMP, SHOW_LABEL, SHOW_NAME };32 33/*34 * Functions declaration35 */36 37struct via_cputemp_data {38 struct device *hwmon_dev;39 const char *name;40 u8 vrm;41 u32 id;42 u32 msr_temp;43 u32 msr_vid;44};45 46/*47 * Sysfs stuff48 */49 50static ssize_t name_show(struct device *dev, struct device_attribute *devattr,51 char *buf)52{53 int ret;54 struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);55 struct via_cputemp_data *data = dev_get_drvdata(dev);56 57 if (attr->index == SHOW_NAME)58 ret = sprintf(buf, "%s\n", data->name);59 else /* show label */60 ret = sprintf(buf, "Core %d\n", data->id);61 return ret;62}63 64static ssize_t temp_show(struct device *dev, struct device_attribute *devattr,65 char *buf)66{67 struct via_cputemp_data *data = dev_get_drvdata(dev);68 u32 eax, edx;69 int err;70 71 err = rdmsr_safe_on_cpu(data->id, data->msr_temp, &eax, &edx);72 if (err)73 return -EAGAIN;74 75 return sprintf(buf, "%lu\n", ((unsigned long)eax & 0xffffff) * 1000);76}77 78static ssize_t cpu0_vid_show(struct device *dev,79 struct device_attribute *devattr, char *buf)80{81 struct via_cputemp_data *data = dev_get_drvdata(dev);82 u32 eax, edx;83 int err;84 85 err = rdmsr_safe_on_cpu(data->id, data->msr_vid, &eax, &edx);86 if (err)87 return -EAGAIN;88 89 return sprintf(buf, "%d\n", vid_from_reg(~edx & 0x7f, data->vrm));90}91 92static SENSOR_DEVICE_ATTR_RO(temp1_input, temp, SHOW_TEMP);93static SENSOR_DEVICE_ATTR_RO(temp1_label, name, SHOW_LABEL);94static SENSOR_DEVICE_ATTR_RO(name, name, SHOW_NAME);95 96static struct attribute *via_cputemp_attributes[] = {97 &sensor_dev_attr_name.dev_attr.attr,98 &sensor_dev_attr_temp1_label.dev_attr.attr,99 &sensor_dev_attr_temp1_input.dev_attr.attr,100 NULL101};102 103static const struct attribute_group via_cputemp_group = {104 .attrs = via_cputemp_attributes,105};106 107/* Optional attributes */108static DEVICE_ATTR_RO(cpu0_vid);109 110static int via_cputemp_probe(struct platform_device *pdev)111{112 struct via_cputemp_data *data;113 struct cpuinfo_x86 *c = &cpu_data(pdev->id);114 int err;115 u32 eax, edx;116 117 data = devm_kzalloc(&pdev->dev, sizeof(struct via_cputemp_data),118 GFP_KERNEL);119 if (!data)120 return -ENOMEM;121 122 data->id = pdev->id;123 data->name = "via_cputemp";124 125 if (c->x86 == 7) {126 data->msr_temp = 0x1423;127 } else {128 switch (c->x86_model) {129 case 0xA:130 /* C7 A */131 case 0xD:132 /* C7 D */133 data->msr_temp = 0x1169;134 data->msr_vid = 0x198;135 break;136 case 0xF:137 /* Nano */138 data->msr_temp = 0x1423;139 break;140 default:141 return -ENODEV;142 }143 }144 145 /* test if we can access the TEMPERATURE MSR */146 err = rdmsr_safe_on_cpu(data->id, data->msr_temp, &eax, &edx);147 if (err) {148 dev_err(&pdev->dev,149 "Unable to access TEMPERATURE MSR, giving up\n");150 return err;151 }152 153 platform_set_drvdata(pdev, data);154 155 err = sysfs_create_group(&pdev->dev.kobj, &via_cputemp_group);156 if (err)157 return err;158 159 if (data->msr_vid)160 data->vrm = vid_which_vrm();161 162 if (data->vrm) {163 err = device_create_file(&pdev->dev, &dev_attr_cpu0_vid);164 if (err)165 goto exit_remove;166 }167 168 data->hwmon_dev = hwmon_device_register(&pdev->dev);169 if (IS_ERR(data->hwmon_dev)) {170 err = PTR_ERR(data->hwmon_dev);171 dev_err(&pdev->dev, "Class registration failed (%d)\n",172 err);173 goto exit_remove;174 }175 176 return 0;177 178exit_remove:179 if (data->vrm)180 device_remove_file(&pdev->dev, &dev_attr_cpu0_vid);181 sysfs_remove_group(&pdev->dev.kobj, &via_cputemp_group);182 return err;183}184 185static void via_cputemp_remove(struct platform_device *pdev)186{187 struct via_cputemp_data *data = platform_get_drvdata(pdev);188 189 hwmon_device_unregister(data->hwmon_dev);190 if (data->vrm)191 device_remove_file(&pdev->dev, &dev_attr_cpu0_vid);192 sysfs_remove_group(&pdev->dev.kobj, &via_cputemp_group);193}194 195static struct platform_driver via_cputemp_driver = {196 .driver = {197 .name = DRVNAME,198 },199 .probe = via_cputemp_probe,200 .remove_new = via_cputemp_remove,201};202 203struct pdev_entry {204 struct list_head list;205 struct platform_device *pdev;206 unsigned int cpu;207};208 209static LIST_HEAD(pdev_list);210static DEFINE_MUTEX(pdev_list_mutex);211 212static int via_cputemp_online(unsigned int cpu)213{214 int err;215 struct platform_device *pdev;216 struct pdev_entry *pdev_entry;217 218 pdev = platform_device_alloc(DRVNAME, cpu);219 if (!pdev) {220 err = -ENOMEM;221 pr_err("Device allocation failed\n");222 goto exit;223 }224 225 pdev_entry = kzalloc(sizeof(struct pdev_entry), GFP_KERNEL);226 if (!pdev_entry) {227 err = -ENOMEM;228 goto exit_device_put;229 }230 231 err = platform_device_add(pdev);232 if (err) {233 pr_err("Device addition failed (%d)\n", err);234 goto exit_device_free;235 }236 237 pdev_entry->pdev = pdev;238 pdev_entry->cpu = cpu;239 mutex_lock(&pdev_list_mutex);240 list_add_tail(&pdev_entry->list, &pdev_list);241 mutex_unlock(&pdev_list_mutex);242 243 return 0;244 245exit_device_free:246 kfree(pdev_entry);247exit_device_put:248 platform_device_put(pdev);249exit:250 return err;251}252 253static int via_cputemp_down_prep(unsigned int cpu)254{255 struct pdev_entry *p;256 257 mutex_lock(&pdev_list_mutex);258 list_for_each_entry(p, &pdev_list, list) {259 if (p->cpu == cpu) {260 platform_device_unregister(p->pdev);261 list_del(&p->list);262 mutex_unlock(&pdev_list_mutex);263 kfree(p);264 return 0;265 }266 }267 mutex_unlock(&pdev_list_mutex);268 return 0;269}270 271static const struct x86_cpu_id __initconst cputemp_ids[] = {272 X86_MATCH_VENDOR_FAM_MODEL(CENTAUR, 6, X86_CENTAUR_FAM6_C7_A, NULL),273 X86_MATCH_VENDOR_FAM_MODEL(CENTAUR, 6, X86_CENTAUR_FAM6_C7_D, NULL),274 X86_MATCH_VENDOR_FAM_MODEL(CENTAUR, 6, X86_CENTAUR_FAM6_NANO, NULL),275 X86_MATCH_VENDOR_FAM_MODEL(CENTAUR, 7, X86_MODEL_ANY, NULL),276 {}277};278MODULE_DEVICE_TABLE(x86cpu, cputemp_ids);279 280static enum cpuhp_state via_temp_online;281 282static int __init via_cputemp_init(void)283{284 int err;285 286 if (!x86_match_cpu(cputemp_ids))287 return -ENODEV;288 289 err = platform_driver_register(&via_cputemp_driver);290 if (err)291 goto exit;292 293 err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "hwmon/via:online",294 via_cputemp_online, via_cputemp_down_prep);295 if (err < 0)296 goto exit_driver_unreg;297 via_temp_online = err;298 299#ifndef CONFIG_HOTPLUG_CPU300 if (list_empty(&pdev_list)) {301 err = -ENODEV;302 goto exit_hp_unreg;303 }304#endif305 return 0;306 307#ifndef CONFIG_HOTPLUG_CPU308exit_hp_unreg:309 cpuhp_remove_state_nocalls(via_temp_online);310#endif311exit_driver_unreg:312 platform_driver_unregister(&via_cputemp_driver);313exit:314 return err;315}316 317static void __exit via_cputemp_exit(void)318{319 cpuhp_remove_state(via_temp_online);320 platform_driver_unregister(&via_cputemp_driver);321}322 323MODULE_AUTHOR("Harald Welte <HaraldWelte@viatech.com>");324MODULE_DESCRIPTION("VIA CPU temperature monitor");325MODULE_LICENSE("GPL");326 327module_init(via_cputemp_init)328module_exit(via_cputemp_exit)329